primer_view_components 0.0.17 → 0.0.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -0
  3. data/app/assets/javascripts/primer_view_components.js +2 -0
  4. data/app/assets/javascripts/primer_view_components.js.map +1 -0
  5. data/app/components/primer/avatar_component.rb +27 -9
  6. data/app/components/primer/avatar_stack_component.html.erb +10 -0
  7. data/app/components/primer/avatar_stack_component.rb +81 -0
  8. data/app/components/primer/base_component.rb +8 -5
  9. data/app/components/primer/blankslate_component.html.erb +3 -3
  10. data/app/components/primer/blankslate_component.rb +18 -25
  11. data/app/components/primer/border_box_component.html.erb +4 -18
  12. data/app/components/primer/border_box_component.rb +75 -68
  13. data/app/components/primer/box_component.rb +10 -0
  14. data/app/components/primer/breadcrumb_component.rb +3 -2
  15. data/app/components/primer/button_component.rb +3 -3
  16. data/app/components/primer/button_group_component.html.erb +5 -0
  17. data/app/components/primer/button_group_component.rb +37 -0
  18. data/app/components/primer/button_marketing_component.rb +73 -0
  19. data/app/components/primer/component.rb +16 -0
  20. data/app/components/primer/counter_component.rb +16 -9
  21. data/app/components/primer/details_component.html.erb +2 -6
  22. data/app/components/primer/details_component.rb +28 -37
  23. data/app/components/primer/dropdown/menu_component.html.erb +12 -0
  24. data/app/components/primer/dropdown/menu_component.rb +48 -0
  25. data/app/components/primer/dropdown_component.html.erb +9 -0
  26. data/app/components/primer/dropdown_component.rb +75 -0
  27. data/app/components/primer/dropdown_menu_component.rb +35 -3
  28. data/app/components/primer/flash_component.html.erb +4 -7
  29. data/app/components/primer/flash_component.rb +18 -17
  30. data/app/components/primer/flex_component.rb +47 -9
  31. data/app/components/primer/flex_item_component.rb +16 -1
  32. data/app/components/primer/heading_component.rb +9 -0
  33. data/app/components/primer/label_component.rb +6 -6
  34. data/app/components/primer/layout_component.rb +2 -2
  35. data/app/components/primer/link_component.rb +6 -2
  36. data/app/components/primer/markdown_component.rb +293 -0
  37. data/app/components/primer/menu_component.html.erb +6 -0
  38. data/app/components/primer/menu_component.rb +71 -0
  39. data/app/components/primer/octicon_component.rb +13 -6
  40. data/app/components/primer/popover_component.rb +5 -3
  41. data/app/components/primer/primer.js +1 -0
  42. data/app/components/primer/primer.ts +1 -0
  43. data/app/components/primer/progress_bar_component.rb +6 -6
  44. data/app/components/primer/spinner_component.rb +8 -5
  45. data/app/components/primer/state_component.rb +23 -12
  46. data/app/components/primer/subhead_component.rb +6 -3
  47. data/app/components/primer/tab_container_component.js +1 -0
  48. data/app/components/primer/tab_container_component.rb +41 -0
  49. data/app/components/primer/tab_container_component.ts +1 -0
  50. data/app/components/primer/tab_nav_component.html.erb +17 -0
  51. data/app/components/primer/tab_nav_component.rb +108 -0
  52. data/app/components/primer/text_component.rb +1 -1
  53. data/app/components/primer/timeline_item_component.html.erb +4 -16
  54. data/app/components/primer/timeline_item_component.rb +41 -49
  55. data/app/components/primer/tooltip_component.rb +88 -0
  56. data/app/components/primer/truncate_component.rb +41 -0
  57. data/app/components/primer/underline_nav_component.rb +26 -1
  58. data/{lib → app/lib}/primer/class_name_helper.rb +1 -0
  59. data/app/lib/primer/classify.rb +280 -0
  60. data/app/lib/primer/classify/cache.rb +125 -0
  61. data/{lib → app/lib}/primer/fetch_or_fallback_helper.rb +1 -0
  62. data/{lib → app/lib}/primer/join_style_arguments_helper.rb +1 -0
  63. data/app/lib/primer/view_helper.rb +22 -0
  64. data/app/lib/primer/view_helper/dsl.rb +34 -0
  65. data/lib/primer/view_components.rb +32 -0
  66. data/lib/primer/view_components/engine.rb +11 -2
  67. data/lib/primer/view_components/version.rb +5 -1
  68. data/lib/yard/renders_many_handler.rb +19 -0
  69. data/lib/yard/renders_one_handler.rb +19 -0
  70. data/static/statuses.json +1 -0
  71. metadata +94 -24
  72. data/app/components/primer/view_components.rb +0 -52
  73. data/lib/primer/classify.rb +0 -250
@@ -3,6 +3,8 @@
3
3
  module Primer
4
4
  # Renders an [Octicon](https://primer.style/octicons/) with <%= link_to_system_arguments_docs %>.
5
5
  class OcticonComponent < Primer::Component
6
+ view_helper :octicon
7
+
6
8
  include Primer::ClassNameHelper
7
9
  include OcticonsHelper
8
10
 
@@ -10,24 +12,25 @@ module Primer
10
12
  SIZE_MAPPINGS = {
11
13
  SIZE_DEFAULT => 16,
12
14
  :medium => 32,
13
- :large => 64,
15
+ :large => 64
14
16
  }.freeze
15
17
  SIZE_OPTIONS = SIZE_MAPPINGS.keys
16
18
 
17
- # @example 25|Default
19
+ # @example Default
18
20
  # <%= render(Primer::OcticonComponent.new(icon: "check")) %>
19
21
  #
20
- # @example 40|Medium
22
+ # @example Medium
21
23
  # <%= render(Primer::OcticonComponent.new(icon: "people", size: :medium)) %>
22
24
  #
23
- # @example 80|Large
25
+ # @example Large
24
26
  # <%= render(Primer::OcticonComponent.new(icon: "x", size: :large)) %>
25
27
  #
26
28
  # @param icon [String] Name of [Octicon](https://primer.style/octicons/) to use.
27
29
  # @param size [Symbol] <%= one_of(Primer::OcticonComponent::SIZE_MAPPINGS) %>
28
30
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
29
31
  def initialize(icon:, size: SIZE_DEFAULT, **system_arguments)
30
- @icon, @system_arguments = icon, system_arguments
32
+ @icon = icon
33
+ @system_arguments = system_arguments
31
34
 
32
35
  @system_arguments[:class] = Primer::Classify.call(**@system_arguments)[:class]
33
36
  @system_arguments[:height] ||= SIZE_MAPPINGS[size]
@@ -39,7 +42,11 @@ module Primer
39
42
  end
40
43
 
41
44
  def call
42
- octicon(@icon, **@system_arguments)
45
+ octicon(@icon, { **@system_arguments })
46
+ end
47
+
48
+ def self.status
49
+ Primer::Component::STATUSES[:beta]
43
50
  end
44
51
  end
45
52
  end
@@ -10,7 +10,7 @@ module Primer
10
10
  with_slot :heading, class_name: "Heading"
11
11
  with_slot :body, class_name: "Body"
12
12
 
13
- # @example 150|Default
13
+ # @example Default
14
14
  # <%= render Primer::PopoverComponent.new do |component| %>
15
15
  # <% component.slot(:heading) do %>
16
16
  # Activity feed
@@ -20,7 +20,7 @@ module Primer
20
20
  # <% end %>
21
21
  # <% end %>
22
22
  #
23
- # @example 150|Large
23
+ # @example Large
24
24
  # <%= render Primer::PopoverComponent.new do |component| %>
25
25
  # <% component.slot(:heading) do %>
26
26
  # Activity feed
@@ -30,7 +30,7 @@ module Primer
30
30
  # <% end %>
31
31
  # <% end %>
32
32
  #
33
- # @example 150|Caret position
33
+ # @example Caret position
34
34
  # <%= render Primer::PopoverComponent.new do |component| %>
35
35
  # <% component.slot(:heading) do %>
36
36
  # Activity feed
@@ -57,6 +57,7 @@ module Primer
57
57
  body.present?
58
58
  end
59
59
 
60
+ # :nodoc:
60
61
  class Heading < Primer::Slot
61
62
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
62
63
  def initialize(**system_arguments)
@@ -70,6 +71,7 @@ module Primer
70
71
  end
71
72
  end
72
73
 
74
+ # :nodoc:
73
75
  class Body < Slot
74
76
  CARET_DEFAULT = :top
75
77
  CARET_MAPPINGS = {
@@ -0,0 +1 @@
1
+ import './tab_container_component';
@@ -0,0 +1 @@
1
+ import './tab_container_component'
@@ -12,26 +12,26 @@ module Primer
12
12
  SIZE_MAPPINGS = {
13
13
  SIZE_DEFAULT => "",
14
14
  :small => "Progress--small",
15
- :large => "Progress--large",
15
+ :large => "Progress--large"
16
16
  }.freeze
17
17
 
18
18
  SIZE_OPTIONS = SIZE_MAPPINGS.keys
19
- # @example 20|Default
19
+ # @example Default
20
20
  # <%= render(Primer::ProgressBarComponent.new) do |component| %>
21
21
  # <% component.slot(:item, percentage: 25) %>
22
22
  # <% end %>
23
23
  #
24
- # @example 20|Small
24
+ # @example Small
25
25
  # <%= render(Primer::ProgressBarComponent.new(size: :small)) do |component| %>
26
26
  # <% component.slot(:item, bg: :blue_4, percentage: 50) %>
27
27
  # <% end %>
28
28
  #
29
- # @example 30|Large
29
+ # @example Large
30
30
  # <%= render(Primer::ProgressBarComponent.new(size: :large)) do |component| %>
31
31
  # <% component.slot(:item, bg: :red_4, percentage: 75) %>
32
32
  # <% end %>
33
33
  #
34
- # @example 20|Multiple items
34
+ # @example Multiple items
35
35
  # <%= render(Primer::ProgressBarComponent.new) do |component| %>
36
36
  # <% component.slot(:item, percentage: 10) %>
37
37
  # <% component.slot(:item, bg: :blue_4, percentage: 20) %>
@@ -48,13 +48,13 @@ module Primer
48
48
  SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, SIZE_DEFAULT)]
49
49
  )
50
50
  @system_arguments[:tag] = :span
51
-
52
51
  end
53
52
 
54
53
  def render?
55
54
  items.any?
56
55
  end
57
56
 
57
+ # :nodoc:
58
58
  class Item < Primer::Slot
59
59
  attr_reader :system_arguments
60
60
 
@@ -3,12 +3,11 @@
3
3
  module Primer
4
4
  # Use Primer::SpinnerComponent to let users know that content is being loaded.
5
5
  class SpinnerComponent < Primer::Component
6
-
7
6
  DEFAULT_SIZE = :medium
8
7
  SIZE_MAPPINGS = {
9
8
  :small => 16,
10
9
  DEFAULT_SIZE => 32,
11
- :large => 64,
10
+ :large => 64
12
11
  }.freeze
13
12
  SIZE_OPTIONS = SIZE_MAPPINGS.keys
14
13
  # Setting `box-sizing: content-box` allows consumers to add padding
@@ -16,13 +15,13 @@ module Primer
16
15
  DEFAULT_STYLE = "box-sizing: content-box; color: var(--color-icon-primary);"
17
16
 
18
17
  #
19
- # @example 48|Default
18
+ # @example Default
20
19
  # <%= render(Primer::SpinnerComponent.new) %>
21
20
  #
22
- # @example 32|Small
21
+ # @example Small
23
22
  # <%= render(Primer::SpinnerComponent.new(size: :small)) %>
24
23
  #
25
- # @example 80|Large
24
+ # @example Large
26
25
  # <%= render(Primer::SpinnerComponent.new(size: :large)) %>
27
26
  #
28
27
  # @param size [Symbol] <%= one_of(Primer::SpinnerComponent::SIZE_MAPPINGS) %>
@@ -35,5 +34,9 @@ module Primer
35
34
  @system_arguments[:viewBox] = "0 0 16 16"
36
35
  @system_arguments[:fill] = :none
37
36
  end
37
+
38
+ def self.status
39
+ Primer::Component::STATUSES[:beta]
40
+ end
38
41
  end
39
42
  end
@@ -4,34 +4,41 @@ module Primer
4
4
  # Component for rendering the status of an item.
5
5
  class StateComponent < Primer::Component
6
6
  COLOR_DEFAULT = :default
7
- COLOR_MAPPINGS = {
7
+ NEW_COLOR_MAPPINGS = {
8
+ open: "State--open",
9
+ closed: "State--closed",
10
+ merged: "State--merged"
11
+ }.freeze
12
+
13
+ DEPRECATED_COLOR_MAPPINGS = {
8
14
  COLOR_DEFAULT => "",
9
- :green => "State--green",
10
- :red => "State--red",
11
- :purple => "State--purple",
15
+ :green => "State--open",
16
+ :red => "State--closed",
17
+ :purple => "State--merged"
12
18
  }.freeze
19
+ COLOR_MAPPINGS = NEW_COLOR_MAPPINGS.merge(DEPRECATED_COLOR_MAPPINGS)
13
20
  COLOR_OPTIONS = COLOR_MAPPINGS.keys
14
21
 
15
22
  SIZE_DEFAULT = :default
16
23
  SIZE_MAPPINGS = {
17
24
  SIZE_DEFAULT => "",
18
- :small => "State--small",
25
+ :small => "State--small"
19
26
  }.freeze
20
27
  SIZE_OPTIONS = SIZE_MAPPINGS.keys
21
28
 
22
29
  TAG_DEFAULT = :span
23
- TAG_OPTIONS = [TAG_DEFAULT, :div, :a]
30
+ TAG_OPTIONS = [TAG_DEFAULT, :div, :a].freeze
24
31
 
25
- # @example 40|Default
32
+ # @example Default
26
33
  # <%= render(Primer::StateComponent.new(title: "title")) { "State" } %>
27
34
  #
28
- # @example 40|Colors
35
+ # @example Colors
29
36
  # <%= render(Primer::StateComponent.new(title: "title")) { "Default" } %>
30
- # <%= render(Primer::StateComponent.new(title: "title", color: :green)) { "Green" } %>
31
- # <%= render(Primer::StateComponent.new(title: "title", color: :red)) { "Red" } %>
32
- # <%= render(Primer::StateComponent.new(title: "title", color: :purple)) { "Purple" } %>
37
+ # <%= render(Primer::StateComponent.new(title: "title", color: :open)) { "Open" } %>
38
+ # <%= render(Primer::StateComponent.new(title: "title", color: :closed)) { "Closed" } %>
39
+ # <%= render(Primer::StateComponent.new(title: "title", color: :merged)) { "Merged" } %>
33
40
  #
34
- # @example 40|Sizes
41
+ # @example Sizes
35
42
  # <%= render(Primer::StateComponent.new(title: "title")) { "Default" } %>
36
43
  # <%= render(Primer::StateComponent.new(title: "title", size: :small)) { "Small" } %>
37
44
  #
@@ -61,5 +68,9 @@ module Primer
61
68
  def call
62
69
  render(Primer::BaseComponent.new(**@system_arguments)) { content }
63
70
  end
71
+
72
+ def self.status
73
+ Primer::Component::STATUSES[:beta]
74
+ end
64
75
  end
65
76
  end
@@ -9,7 +9,7 @@ module Primer
9
9
  with_slot :actions, class_name: "Actions"
10
10
  with_slot :description, class_name: "Description"
11
11
 
12
- # @example 95|Default
12
+ # @example Default
13
13
  # <%= render(Primer::SubheadComponent.new) do |component| %>
14
14
  # <% component.slot(:heading) do %>
15
15
  # My Heading
@@ -19,7 +19,7 @@ module Primer
19
19
  # <% end %>
20
20
  # <% end %>
21
21
  #
22
- # @example 95|Without border
22
+ # @example Without border
23
23
  # <%= render(Primer::SubheadComponent.new(hide_border: true)) do |component| %>
24
24
  # <% component.slot(:heading) do %>
25
25
  # My Heading
@@ -29,7 +29,7 @@ module Primer
29
29
  # <% end %>
30
30
  # <% end %>
31
31
  #
32
- # @example 95|With actions
32
+ # @example With actions
33
33
  # <%= render(Primer::SubheadComponent.new) do |component| %>
34
34
  # <% component.slot(:heading) do %>
35
35
  # My Heading
@@ -67,6 +67,7 @@ module Primer
67
67
  heading.present?
68
68
  end
69
69
 
70
+ # :nodoc:
70
71
  class Heading < ViewComponent::Slot
71
72
  include ClassNameHelper
72
73
 
@@ -85,6 +86,7 @@ module Primer
85
86
  end
86
87
  end
87
88
 
89
+ # :nodoc:
88
90
  class Actions < ViewComponent::Slot
89
91
  include ClassNameHelper
90
92
 
@@ -98,6 +100,7 @@ module Primer
98
100
  end
99
101
  end
100
102
 
103
+ # :nodoc:
101
104
  class Description < ViewComponent::Slot
102
105
  include ClassNameHelper
103
106
 
@@ -0,0 +1 @@
1
+ import '@github/tab-container-element';
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ # Use TabContainer to create tabbed content with keyboard support. This component does not add any styles.
5
+ # It only provides the tab functionality. If you want styled Tabs you can look at <%= link_to_component(Primer::TabNavComponent) %>.
6
+ #
7
+ # This component requires javascript.
8
+ class TabContainerComponent < Primer::Component
9
+ # @example Default
10
+ # <%= render(Primer::TabContainerComponent.new) do %>
11
+ # <div role="tablist">
12
+ # <button type="button" role="tab" aria-selected="true">Tab one</button>
13
+ # <button type="button" role="tab" tabindex="-1">Tab two</button>
14
+ # <button type="button" role="tab" tabindex="-1">Tab three</button>
15
+ # </div>
16
+ # <div role="tabpanel">
17
+ # Panel 1
18
+ # </div>
19
+ # <div role="tabpanel" hidden>
20
+ # Panel 2
21
+ # </div>
22
+ # <div role="tabpanel" hidden>
23
+ # Panel 3
24
+ # </div>
25
+ # <% end %>
26
+ #
27
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
28
+ def initialize(**system_arguments)
29
+ @system_arguments = system_arguments
30
+ @system_arguments[:tag] = "tab-container"
31
+ end
32
+
33
+ def call
34
+ render(Primer::BaseComponent.new(**@system_arguments)) { content }
35
+ end
36
+
37
+ def render?
38
+ content.present?
39
+ end
40
+ end
41
+ end
@@ -0,0 +1 @@
1
+ import '@github/tab-container-element'
@@ -0,0 +1,17 @@
1
+ <%= render wrapper.new(**@system_arguments) do %>
2
+ <nav role="tablist" aria-label="<%= @aria_label %>" class="tabnav-tabs">
3
+ <% tabs.each do |tab| %>
4
+ <%= tab %>
5
+ <% end %>
6
+ </nav >
7
+
8
+ <% if @with_panel %>
9
+ <% tabs.each do |tab| %>
10
+ <% if tab.panel.present? %>
11
+ <div role="tabpanel" <%= "hidden" if tab.hidden? %>>
12
+ <%= tab.panel %>
13
+ </div>
14
+ <% end %>
15
+ <% end %>
16
+ <% end %>
17
+ <% end %>
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ # Use TabNav to style navigation with a tab-based selected state, typically used for navigation placed at the top of the page.
5
+ class TabNavComponent < Primer::Component
6
+ include ViewComponent::SlotableV2
7
+
8
+ class MultipleSelectedTabsError < StandardError; end
9
+ class NoSelectedTabsError < StandardError; end
10
+
11
+ # Tabs to be rendered.
12
+ #
13
+ # @param title [String] Text to be rendered by the tab.
14
+ # @param selected [Boolean] Whether the tab is selected.
15
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
16
+ renders_many :tabs, lambda { |**system_arguments|
17
+ return TabComponent.new(**system_arguments) unless @with_panel
18
+
19
+ TabComponent.new(tag: :button, type: :button, **system_arguments)
20
+ }
21
+
22
+ # @example Default
23
+ # <%= render(Primer::TabNavComponent.new) do |c| %>
24
+ # <% c.tab(selected: true, title: "Tab 1", href: "#") %>
25
+ # <% c.tab(title: "Tab 2", href: "#") %>
26
+ # <% c.tab(title: "Tab 3", href: "#") %>
27
+ # <% end %>
28
+ #
29
+ # @example With panels
30
+ # <%= render(Primer::TabNavComponent.new(with_panel: true)) do |c| %>
31
+ # <% c.tab(selected: true, title: "Tab 1") { "Panel 1" } %>
32
+ # <% c.tab(title: "Tab 2") { "Panel 1" } %>
33
+ # <% c.tab(title: "Tab 3") { "Panel 1" } %>
34
+ # <% end %>
35
+ #
36
+ # @param aria_label [String] Used to set the `aria-label` on the top level `<nav>` element.
37
+ # @param with_panel [Boolean] Whether the TabNav should navigate through pages or panels.
38
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
39
+ def initialize(aria_label: nil, with_panel: false, **system_arguments)
40
+ @aria_label = aria_label
41
+ @with_panel = with_panel
42
+ @system_arguments = system_arguments
43
+ @system_arguments[:tag] ||= :div
44
+
45
+ @system_arguments[:classes] = class_names(
46
+ "tabnav",
47
+ system_arguments[:classes]
48
+ )
49
+ end
50
+
51
+ def before_render
52
+ validate_single_selected_tab
53
+ end
54
+
55
+ private
56
+
57
+ def wrapper
58
+ @with_panel ? Primer::TabContainerComponent : Primer::BaseComponent
59
+ end
60
+
61
+ def validate_single_selected_tab
62
+ raise MultipleSelectedTabsError, "only one tab can be selected" if selected_tabs_count > 1
63
+ raise NoSelectedTabsError, "a tab must be selected" if selected_tabs_count != 1
64
+ end
65
+
66
+ def selected_tabs_count
67
+ @selected_tabs_count ||= tabs.count(&:selected)
68
+ end
69
+
70
+ # Tabs to be rendered.
71
+ class TabComponent < Primer::Component
72
+ attr_reader :selected
73
+
74
+ def initialize(title:, selected: false, **system_arguments)
75
+ @title = title
76
+ @selected = selected
77
+ @system_arguments = system_arguments
78
+ @system_arguments[:tag] ||= :a
79
+ @system_arguments[:role] = :tab
80
+
81
+ if selected
82
+ if @system_arguments[:tag] == :a
83
+ @system_arguments[:"aria-current"] = :page
84
+ else
85
+ @system_arguments[:"aria-selected"] = true
86
+ end
87
+ end
88
+
89
+ @system_arguments[:classes] = class_names(
90
+ "tabnav-tab",
91
+ system_arguments[:classes]
92
+ )
93
+ end
94
+
95
+ def call
96
+ render(Primer::BaseComponent.new(**@system_arguments)) { @title }
97
+ end
98
+
99
+ def panel
100
+ content
101
+ end
102
+
103
+ def hidden?
104
+ !@selected
105
+ end
106
+ end
107
+ end
108
+ end