primer_view_components 0.0.1 → 0.0.3

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -1
  3. data/README.md +135 -0
  4. data/app/components/primer/avatar_component.rb +28 -0
  5. data/app/components/primer/base_component.rb +42 -0
  6. data/app/components/primer/blankslate_component.html.erb +27 -0
  7. data/app/components/primer/blankslate_component.rb +156 -0
  8. data/app/components/primer/border_box_component.html.erb +26 -0
  9. data/app/components/primer/border_box_component.rb +77 -0
  10. data/app/components/primer/box_component.rb +14 -0
  11. data/app/components/primer/breadcrumb_component.html.erb +8 -0
  12. data/app/components/primer/breadcrumb_component.rb +52 -0
  13. data/app/components/primer/button_component.rb +58 -0
  14. data/app/components/primer/component.rb +9 -0
  15. data/app/components/primer/counter_component.rb +78 -0
  16. data/app/components/primer/details_component.html.erb +6 -0
  17. data/app/components/primer/details_component.rb +38 -0
  18. data/app/components/primer/dropdown_menu_component.html.erb +8 -0
  19. data/app/components/primer/dropdown_menu_component.rb +28 -0
  20. data/app/components/primer/flex_component.rb +81 -0
  21. data/app/components/primer/flex_item_component.rb +21 -0
  22. data/app/components/primer/heading_component.rb +14 -0
  23. data/app/components/primer/label_component.rb +48 -0
  24. data/app/components/primer/layout_component.html.erb +17 -0
  25. data/app/components/primer/layout_component.rb +21 -0
  26. data/app/components/primer/link_component.rb +19 -0
  27. data/app/components/primer/progress_bar_component.html.erb +5 -0
  28. data/app/components/primer/progress_bar_component.rb +66 -0
  29. data/app/components/primer/slot.rb +8 -0
  30. data/app/components/primer/state_component.rb +53 -0
  31. data/app/components/primer/subhead_component.html.erb +17 -0
  32. data/app/components/primer/subhead_component.rb +89 -0
  33. data/app/components/primer/text_component.rb +14 -0
  34. data/app/components/primer/timeline_item_component.html.erb +17 -0
  35. data/app/components/primer/timeline_item_component.rb +69 -0
  36. data/app/components/primer/underline_nav_component.html.erb +11 -0
  37. data/app/components/primer/underline_nav_component.rb +22 -0
  38. data/app/components/primer/view_components.rb +47 -0
  39. data/lib/primer/class_name_helper.rb +27 -0
  40. data/lib/primer/classify.rb +237 -0
  41. data/lib/primer/fetch_or_fallback_helper.rb +41 -0
  42. data/lib/primer/view_components.rb +3 -0
  43. data/lib/primer/view_components/engine.rb +11 -0
  44. data/lib/primer/view_components/version.rb +1 -1
  45. metadata +159 -4
@@ -0,0 +1,26 @@
1
+ <%= render Primer::BaseComponent.new(**@kwargs) do %>
2
+ <% if header %>
3
+ <%= render Primer::BaseComponent.new(**header.kwargs) do %>
4
+ <%= header.content %>
5
+ <% end %>
6
+ <% end %>
7
+ <% if body %>
8
+ <%= render Primer::BaseComponent.new(**body.kwargs) do %>
9
+ <%= body.content %>
10
+ <% end %>
11
+ <% end %>
12
+ <% if rows.any? %>
13
+ <ul>
14
+ <% rows.each do |row| %>
15
+ <%= render Primer::BaseComponent.new(**row.kwargs) do %>
16
+ <%= row.content %>
17
+ <% end %>
18
+ <% end %>
19
+ </ul>
20
+ <% end %>
21
+ <% if footer %>
22
+ <%= render Primer::BaseComponent.new(**footer.kwargs) do %>
23
+ <%= footer.content %>
24
+ <% end %>
25
+ <% end %>
26
+ <% end %>
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class BorderBoxComponent < Primer::Component
5
+ include ViewComponent::Slotable
6
+
7
+ with_slot :header, class_name: "Header"
8
+ with_slot :body, class_name: "Body"
9
+ with_slot :footer, class_name: "Footer"
10
+ with_slot :row, collection: true, class_name: "Row"
11
+
12
+ def initialize(**kwargs)
13
+ @kwargs = kwargs
14
+ @kwargs[:tag] = :div
15
+ @kwargs[:classes] = class_names(
16
+ "Box",
17
+ kwargs[:classes]
18
+ )
19
+ end
20
+
21
+ def render?
22
+ rows.any? || header.present? || body.present? || footer.present?
23
+ end
24
+
25
+ class Header < Primer::Slot
26
+
27
+ attr_reader :kwargs
28
+ def initialize(**kwargs)
29
+ @kwargs = kwargs
30
+ @kwargs[:tag] = :div
31
+ @kwargs[:classes] = class_names(
32
+ "Box-header",
33
+ kwargs[:classes]
34
+ )
35
+ end
36
+ end
37
+
38
+ class Body < Primer::Slot
39
+
40
+ attr_reader :kwargs
41
+ def initialize(**kwargs)
42
+ @kwargs = kwargs
43
+ @kwargs[:tag] = :div
44
+ @kwargs[:classes] = class_names(
45
+ "Box-body",
46
+ kwargs[:classes]
47
+ )
48
+ end
49
+ end
50
+
51
+ class Footer < Primer::Slot
52
+
53
+ attr_reader :kwargs
54
+ def initialize(**kwargs)
55
+ @kwargs = kwargs
56
+ @kwargs[:tag] = :div
57
+ @kwargs[:classes] = class_names(
58
+ "Box-footer",
59
+ kwargs[:classes]
60
+ )
61
+ end
62
+ end
63
+
64
+ class Row < Primer::Slot
65
+
66
+ attr_reader :kwargs
67
+ def initialize(**kwargs)
68
+ @kwargs = kwargs
69
+ @kwargs[:tag] = :li
70
+ @kwargs[:classes] = class_names(
71
+ "Box-row",
72
+ kwargs[:classes]
73
+ )
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class BoxComponent < Primer::Component
5
+ def initialize(**kwargs)
6
+ @kwargs = kwargs
7
+ @kwargs[:tag] = :div
8
+ end
9
+
10
+ def call
11
+ render(Primer::BaseComponent.new(**@kwargs)) { content }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ <%= render Primer::BaseComponent.new(**@kwargs) do %>
2
+ <ol>
3
+ <% items.each do |item| %>
4
+ <%# The <li> and <a> need to be on the same line to prevent unwanted whitespace %>
5
+ <%= render Primer::BaseComponent.new(**item.kwargs) do %><%- if item.href.present? %><a href="<%= item.href %>"><%= item.content %></a><%- else %><%= item.content %><%- end %><%- end %>
6
+ <% end %>
7
+ </ol>
8
+ <% end %>
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Breadcrumbs are used to display page hierarchy within a section of the site. All of the items in the breadcrumb "trail" are links except for the final item which is a plain string indicating the current page.
5
+ #
6
+ # ## Example
7
+ #
8
+ # The `Primer::BreadcrumbComponent` uses the [Slots API](https://github.com/github/view_component#slots-experimental) and at least one slot is required for the component to render. Each slot can accept the following parameters:
9
+ #
10
+ # 1. `href` (string). The URL to link to.
11
+ # 2. `selected` (boolean, default=false). Flag indicating whether or not the item is selected and not rendered as a link.
12
+ #
13
+ # Note that if if both `href` and `selected: true` are passed in, `href` will be ignored and the item will not be rendered as a link.
14
+ #
15
+ # ```ruby
16
+ # <%= render(Primer::BreadcrumbComponent.new) do |component| %>
17
+ # <% component.slot(:item, href: "/") do %>Home<% end %>
18
+ # <% component.slot(:item, href: "/about") do %>About<% end %>
19
+ # <% component.slot(:item, selected: true) do %>Team<% end %>
20
+ # <% end %>
21
+ # ```
22
+ ##
23
+ module Primer
24
+ class BreadcrumbComponent < Primer::Component
25
+ include ViewComponent::Slotable
26
+
27
+ with_slot :item, collection: true, class_name: "BreadcrumbItem"
28
+
29
+ def initialize(**kwargs)
30
+ @kwargs = kwargs
31
+ @kwargs[:tag] = :nav
32
+ @kwargs[:aria] = { label: "Breadcrumb" }
33
+ end
34
+
35
+ def render?
36
+ items.any?
37
+ end
38
+
39
+ class BreadcrumbItem < Primer::Slot
40
+ attr_reader :href, :kwargs
41
+
42
+ def initialize(href: nil, selected: false, **kwargs)
43
+ @href, @kwargs = href, kwargs
44
+
45
+ @href = nil if selected
46
+ @kwargs[:tag] = :li
47
+ @kwargs[:"aria-current"] = "page" if selected
48
+ @kwargs[:classes] = "breadcrumb-item #{@kwargs[:classes]}"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class ButtonComponent < Primer::Component
5
+ DEFAULT_BUTTON_TYPE = :default
6
+ BUTTON_TYPE_MAPPINGS = {
7
+ DEFAULT_BUTTON_TYPE => "",
8
+ :primary => "btn-primary",
9
+ :danger => "btn-danger",
10
+ :outline => "btn-outline"
11
+ }.freeze
12
+ BUTTON_TYPE_OPTIONS = BUTTON_TYPE_MAPPINGS.keys
13
+
14
+ DEFAULT_VARIANT = :medium
15
+ VARIANT_MAPPINGS = {
16
+ :small => "btn-sm",
17
+ DEFAULT_VARIANT => "",
18
+ :large => "btn-large",
19
+ }.freeze
20
+ VARIANT_OPTIONS = VARIANT_MAPPINGS.keys
21
+
22
+ DEFAULT_TAG = :button
23
+ TAG_OPTIONS = [DEFAULT_TAG, :a].freeze
24
+
25
+ DEFAULT_TYPE = :button
26
+ TYPE_OPTIONS = [DEFAULT_TYPE, :reset, :submit].freeze
27
+
28
+ def initialize(
29
+ button_type: DEFAULT_BUTTON_TYPE,
30
+ variant: DEFAULT_VARIANT,
31
+ tag: DEFAULT_TAG,
32
+ type: DEFAULT_TYPE,
33
+ group_item: false,
34
+ **kwargs
35
+ )
36
+ @kwargs = kwargs
37
+ @kwargs[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, DEFAULT_TAG)
38
+
39
+ if @kwargs[:tag] == :a
40
+ @kwargs[:role] = :button
41
+ else
42
+ @kwargs[:type] = type
43
+ end
44
+
45
+ @kwargs[:classes] = class_names(
46
+ "btn",
47
+ kwargs[:classes],
48
+ BUTTON_TYPE_MAPPINGS[fetch_or_fallback(BUTTON_TYPE_OPTIONS, button_type.to_sym, DEFAULT_BUTTON_TYPE)],
49
+ VARIANT_MAPPINGS[fetch_or_fallback(VARIANT_OPTIONS, variant, DEFAULT_VARIANT)],
50
+ group_item ? "BtnGroup-item" : ""
51
+ )
52
+ end
53
+
54
+ def call
55
+ render(Primer::BaseComponent.new(**@kwargs)) { content }
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class Component < ViewComponent::Base
5
+ include ClassNameHelper
6
+ include FetchOrFallbackHelper
7
+ include OcticonsHelper
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class CounterComponent < Primer::Component
5
+ DEFAULT_SCHEME = :default
6
+ SCHEME_MAPPINGS = {
7
+ DEFAULT_SCHEME => "Counter",
8
+ :gray => "Counter Counter--gray",
9
+ :light_gray => "Counter Counter--gray-light",
10
+ }.freeze
11
+
12
+ def initialize(
13
+ count: 0,
14
+ scheme: DEFAULT_SCHEME,
15
+ limit: 5_000,
16
+ hide_if_zero: false,
17
+ text: "",
18
+ round: false,
19
+ **kwargs
20
+ )
21
+ @count, @limit, @hide_if_zero, @text, @round, @kwargs = count, limit, hide_if_zero, text, round, kwargs
22
+
23
+ @kwargs[:title] = title
24
+ @kwargs[:tag] = :span
25
+ @kwargs[:classes] = class_names(
26
+ @kwargs[:classes],
27
+ SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_MAPPINGS.keys, scheme, DEFAULT_SCHEME)]
28
+ )
29
+ if count == 0 && hide_if_zero
30
+ @kwargs[:hidden] = true
31
+ end
32
+ end
33
+
34
+ def call
35
+ render(Primer::BaseComponent.new(**@kwargs)) { value }
36
+ end
37
+
38
+ private
39
+
40
+ def title
41
+ if @text.present?
42
+ @text
43
+ elsif @count.nil?
44
+ "Not available"
45
+ elsif @count == Float::INFINITY
46
+ "Infinity"
47
+ else
48
+ count = @count.to_i
49
+ str = number_with_delimiter([count, @limit].min)
50
+ str += "+" if count > @limit
51
+ str
52
+ end
53
+ end
54
+
55
+ def value
56
+ if @text.present?
57
+ @text
58
+ elsif @count.nil?
59
+ "" # CSS will hide it
60
+ elsif @count == Float::INFINITY
61
+ "∞"
62
+ else
63
+ if @round
64
+ count = [@count.to_i, @limit].min
65
+ precision = count.between?(100_000, 999_999) ? 0 : 1
66
+ units = {thousand: "k", million: "m", billion: "b"}
67
+ str = number_to_human(count, precision: precision, significant: false, units: units, format: "%n%u")
68
+ else
69
+ @count = @count.to_i
70
+ str = number_with_delimiter([@count, @limit].min)
71
+ end
72
+
73
+ str += "+" if @count.to_i > @limit
74
+ str
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,6 @@
1
+ <%= render Primer::BaseComponent.new(**@kwargs) do %>
2
+ <%= render Primer::BaseComponent.new(**@summary_kwargs) do %>
3
+ <%= summary %>
4
+ <% end %>
5
+ <%= body %>
6
+ <% end %>
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ #
5
+ # overlay - options are `none`, `default` and `dark`. Dictates the type of overlay to render with.
6
+ # button - options are `default` and `reset`. default will make the target a default primer ``.btn`
7
+ # reset will remove all styles from the <summary> element.
8
+ #
9
+ class DetailsComponent < Primer::Component
10
+ OVERLAY_DEFAULT = :none
11
+ OVERLAY_MAPPINGS = {
12
+ OVERLAY_DEFAULT => "",
13
+ :default => "details-overlay",
14
+ :dark => "details-overlay details-overlay-dark",
15
+ }.freeze
16
+
17
+ BUTTON_DEFAULT = :default
18
+ BUTTON_RESET = :reset
19
+ BUTTON_OPTIONS = [BUTTON_DEFAULT, BUTTON_RESET]
20
+
21
+ with_content_areas :summary, :body
22
+
23
+ def initialize(overlay: OVERLAY_DEFAULT, button: BUTTON_DEFAULT, **kwargs)
24
+ @button = fetch_or_fallback(BUTTON_OPTIONS, button, BUTTON_DEFAULT)
25
+
26
+ @kwargs = kwargs
27
+ @kwargs[:tag] = :details
28
+ @kwargs[:classes] = class_names(
29
+ kwargs[:classes],
30
+ OVERLAY_MAPPINGS[fetch_or_fallback(OVERLAY_MAPPINGS.keys, overlay, OVERLAY_DEFAULT)],
31
+ "details-reset" => @button == BUTTON_RESET
32
+ )
33
+
34
+ @summary_kwargs = { tag: :summary, role: "button" }
35
+ @summary_kwargs[:classes] = "btn" if @button == BUTTON_DEFAULT
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,8 @@
1
+ <%= render Primer::BaseComponent.new(**@kwargs) do %>
2
+ <% if @header.present? %>
3
+ <div class="dropdown-header">
4
+ <%= @header %>
5
+ </div>
6
+ <% end %>
7
+ <%= content %>
8
+ <% end %>
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class DropdownMenuComponent < Primer::Component
5
+ SCHEME_DEFAULT = :default
6
+ SCHEME_MAPPINGS = {
7
+ SCHEME_DEFAULT => "",
8
+ :dark => "dropdown-menu-dark",
9
+ }.freeze
10
+
11
+ DIRECTION_DEFAULT = :se
12
+ DIRECTION_OPTIONS = [DIRECTION_DEFAULT, :sw, :w, :e, :ne, :s]
13
+
14
+ def initialize(direction: DIRECTION_DEFAULT, scheme: SCHEME_DEFAULT, header: nil, **kwargs)
15
+ @header, @direction, @kwargs = header, direction, kwargs
16
+
17
+ @kwargs[:tag] = "details-menu"
18
+ @kwargs[:role] = "menu"
19
+
20
+ @kwargs[:classes] = class_names(
21
+ @kwargs[:classes],
22
+ "dropdown-menu",
23
+ "dropdown-menu-#{fetch_or_fallback(DIRECTION_OPTIONS, direction, DIRECTION_DEFAULT)}",
24
+ SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_MAPPINGS.keys, scheme, SCHEME_DEFAULT)]
25
+ )
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class FlexComponent < Primer::Component
5
+ JUSTIFY_CONTENT_DEFAULT = nil
6
+ JUSTIFY_CONTENT_MAPPINGS = {
7
+ flex_start: "flex-justify-start",
8
+ flex_end: "flex-justify-end",
9
+ center: "flex-justify-center",
10
+ space_between: "flex-justify-between",
11
+ space_around: "flex-justify-around",
12
+ }
13
+ JUSTIFY_CONTENT_OPTIONS = [JUSTIFY_CONTENT_DEFAULT, *JUSTIFY_CONTENT_MAPPINGS.keys]
14
+
15
+ ALIGN_ITEMS_DEFAULT = nil
16
+ ALIGN_ITEMS_MAPPINGS = {
17
+ start: "flex-items-start",
18
+ end: "flex-items-end",
19
+ center: "flex-items-center",
20
+ baseline: "flex-items-baseline",
21
+ stretch: "flex-items-stretch",
22
+ }
23
+ ALIGN_ITEMS_OPTIONS = [ALIGN_ITEMS_DEFAULT, *ALIGN_ITEMS_MAPPINGS.keys]
24
+
25
+ INLINE_DEFAULT = false
26
+ INLINE_OPTIONS = [INLINE_DEFAULT, true]
27
+
28
+ FLEX_WRAP_DEFAULT = nil
29
+ FLEX_WRAP_OPTIONS = [FLEX_WRAP_DEFAULT, true, false]
30
+
31
+ DEFAULT_DIRECTION = nil
32
+ ALLOWED_DIRECTIONS = [DEFAULT_DIRECTION, :column, :column_reverse, :row, :row_reverse]
33
+
34
+ def initialize(
35
+ justify_content: JUSTIFY_CONTENT_DEFAULT,
36
+ inline: INLINE_DEFAULT,
37
+ flex_wrap: FLEX_WRAP_DEFAULT,
38
+ align_items: ALIGN_ITEMS_DEFAULT,
39
+ direction: nil,
40
+ **kwargs
41
+ )
42
+ @align_items = fetch_or_fallback(ALIGN_ITEMS_OPTIONS, align_items, ALIGN_ITEMS_DEFAULT)
43
+ @justify_content = fetch_or_fallback(JUSTIFY_CONTENT_OPTIONS, justify_content, JUSTIFY_CONTENT_DEFAULT)
44
+ @flex_wrap = fetch_or_fallback(FLEX_WRAP_OPTIONS, flex_wrap, FLEX_WRAP_DEFAULT)
45
+
46
+ # Support direction argument that is an array
47
+ @direction =
48
+ if (Array(direction) - ALLOWED_DIRECTIONS).empty?
49
+ direction
50
+ else
51
+ DEFAULT_DIRECTION
52
+ end
53
+
54
+ @kwargs = kwargs.merge({ direction: @direction }.compact)
55
+ @kwargs[:classes] = class_names(@kwargs[:classes], component_class_names)
56
+ @kwargs[:display] = fetch_or_fallback(INLINE_OPTIONS, inline, INLINE_DEFAULT) ? :inline_flex : :flex
57
+ end
58
+
59
+ def call
60
+ render(Primer::BoxComponent.new(**@kwargs)) { content }
61
+ end
62
+
63
+ private
64
+
65
+ def component_class_names
66
+ out = []
67
+ out << JUSTIFY_CONTENT_MAPPINGS[@justify_content]
68
+ out << ALIGN_ITEMS_MAPPINGS[@align_items]
69
+
70
+ out <<
71
+ case @flex_wrap
72
+ when true
73
+ "flex-wrap"
74
+ when false
75
+ "flex-nowrap"
76
+ end
77
+
78
+ out.compact.join(" ")
79
+ end
80
+ end
81
+ end