primer_view_components 0.0.21 → 0.0.22
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/app/assets/javascripts/primer_view_components.js +2 -0
- data/app/assets/javascripts/primer_view_components.js.map +1 -0
- data/app/components/primer/avatar_component.rb +3 -3
- data/app/components/primer/avatar_stack_component.rb +3 -3
- data/app/components/primer/base_component.rb +1 -1
- data/app/components/primer/blankslate_component.html.erb +2 -2
- data/app/components/primer/blankslate_component.rb +7 -7
- data/app/components/primer/border_box_component.html.erb +4 -18
- data/app/components/primer/border_box_component.rb +58 -67
- data/app/components/primer/box_component.rb +2 -2
- data/app/components/primer/breadcrumb_component.rb +1 -1
- data/app/components/primer/button_component.rb +2 -2
- data/app/components/primer/button_group_component.rb +1 -1
- data/app/components/primer/button_marketing_component.rb +2 -2
- data/app/components/primer/component.rb +4 -0
- data/app/components/primer/counter_component.rb +1 -1
- data/app/components/primer/details_component.html.erb +2 -6
- data/app/components/primer/details_component.rb +22 -35
- data/app/components/primer/dropdown_component.html.erb +2 -2
- data/app/components/primer/dropdown_component.rb +4 -6
- data/app/components/primer/dropdown_menu_component.rb +4 -4
- data/app/components/primer/flash_component.html.erb +2 -2
- data/app/components/primer/flash_component.rb +5 -5
- data/app/components/primer/flex_component.rb +4 -4
- data/app/components/primer/flex_item_component.rb +1 -1
- data/app/components/primer/heading_component.rb +3 -1
- data/app/components/primer/label_component.rb +2 -2
- data/app/components/primer/layout_component.rb +2 -2
- data/app/components/primer/link_component.rb +2 -2
- data/app/components/primer/markdown_component.rb +8 -8
- data/app/components/primer/menu_component.rb +1 -1
- data/app/components/primer/octicon_component.rb +5 -3
- data/app/components/primer/popover_component.rb +3 -3
- data/app/components/primer/primer.js +1 -0
- data/app/components/primer/primer.ts +1 -0
- data/app/components/primer/progress_bar_component.rb +4 -4
- data/app/components/primer/spinner_component.rb +3 -3
- data/app/components/primer/state_component.rb +21 -10
- data/app/components/primer/subhead_component.rb +3 -3
- data/app/components/primer/tab_container_component.js +1 -0
- data/app/components/primer/tab_container_component.rb +41 -0
- data/app/components/primer/tab_container_component.ts +1 -0
- data/app/components/primer/tab_nav_component.html.erb +17 -0
- data/app/components/primer/tab_nav_component.rb +108 -0
- data/app/components/primer/text_component.rb +1 -1
- data/app/components/primer/timeline_item_component.rb +1 -1
- data/app/components/primer/tooltip_component.rb +5 -5
- data/app/components/primer/truncate_component.rb +4 -4
- data/app/components/primer/underline_nav_component.rb +2 -2
- data/{lib → app/lib}/primer/class_name_helper.rb +0 -0
- data/{lib → app/lib}/primer/classify.rb +0 -2
- data/{lib → app/lib}/primer/classify/cache.rb +0 -0
- data/{lib → app/lib}/primer/fetch_or_fallback_helper.rb +0 -0
- data/{lib → app/lib}/primer/join_style_arguments_helper.rb +0 -0
- data/app/lib/primer/view_helper.rb +22 -0
- data/app/lib/primer/view_helper/dsl.rb +34 -0
- data/lib/primer/view_components/engine.rb +10 -2
- data/lib/primer/view_components/version.rb +5 -1
- data/static/statuses.json +1 -1
- metadata +18 -8
- data/app/components/primer/view_components.rb +0 -60
@@ -15,13 +15,13 @@ module Primer
|
|
15
15
|
DEFAULT_STYLE = "box-sizing: content-box; color: var(--color-icon-primary);"
|
16
16
|
|
17
17
|
#
|
18
|
-
# @example
|
18
|
+
# @example Default
|
19
19
|
# <%= render(Primer::SpinnerComponent.new) %>
|
20
20
|
#
|
21
|
-
# @example
|
21
|
+
# @example Small
|
22
22
|
# <%= render(Primer::SpinnerComponent.new(size: :small)) %>
|
23
23
|
#
|
24
|
-
# @example
|
24
|
+
# @example Large
|
25
25
|
# <%= render(Primer::SpinnerComponent.new(size: :large)) %>
|
26
26
|
#
|
27
27
|
# @param size [Symbol] <%= one_of(Primer::SpinnerComponent::SIZE_MAPPINGS) %>
|
@@ -4,12 +4,19 @@ module Primer
|
|
4
4
|
# Component for rendering the status of an item.
|
5
5
|
class StateComponent < Primer::Component
|
6
6
|
COLOR_DEFAULT = :default
|
7
|
-
|
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--
|
10
|
-
:red => "State--
|
11
|
-
:purple => "State--
|
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
|
@@ -22,16 +29,16 @@ module Primer
|
|
22
29
|
TAG_DEFAULT = :span
|
23
30
|
TAG_OPTIONS = [TAG_DEFAULT, :div, :a].freeze
|
24
31
|
|
25
|
-
# @example
|
32
|
+
# @example Default
|
26
33
|
# <%= render(Primer::StateComponent.new(title: "title")) { "State" } %>
|
27
34
|
#
|
28
|
-
# @example
|
35
|
+
# @example Colors
|
29
36
|
# <%= render(Primer::StateComponent.new(title: "title")) { "Default" } %>
|
30
|
-
# <%= render(Primer::StateComponent.new(title: "title", color: :
|
31
|
-
# <%= render(Primer::StateComponent.new(title: "title", color: :
|
32
|
-
# <%= render(Primer::StateComponent.new(title: "title", color: :
|
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
|
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
|
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
|
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
|
32
|
+
# @example With actions
|
33
33
|
# <%= render(Primer::SubheadComponent.new) do |component| %>
|
34
34
|
# <% component.slot(:heading) do %>
|
35
35
|
# My Heading
|
@@ -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
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Primer
|
4
4
|
# The Text component is a wrapper component that will apply typography styles to the text inside.
|
5
5
|
class TextComponent < Primer::Component
|
6
|
-
# @example
|
6
|
+
# @example Default
|
7
7
|
# <%= render(Primer::TextComponent.new(tag: :p, font_weight: :bold)) { "Bold Text" } %>
|
8
8
|
# <%= render(Primer::TextComponent.new(tag: :p, color: :red_5)) { "Red Text" } %>
|
9
9
|
#
|
@@ -36,7 +36,7 @@ module Primer
|
|
36
36
|
Primer::BaseComponent.new(**system_arguments)
|
37
37
|
}
|
38
38
|
|
39
|
-
# @example
|
39
|
+
# @example Default
|
40
40
|
# <div style="padding-left: 60px">
|
41
41
|
# <%= render(Primer::TimelineItemComponent.new) do |component| %>
|
42
42
|
# <% component.avatar(src: "https://github.com/github.png", alt: "github") %>
|
@@ -26,29 +26,29 @@ module Primer
|
|
26
26
|
se
|
27
27
|
]
|
28
28
|
|
29
|
-
# @example
|
29
|
+
# @example Default
|
30
30
|
# <div class="pt-5">
|
31
31
|
# <%= render(Primer::TooltipComponent.new(label: "Even bolder")) { "Default Bold Text" } %>
|
32
32
|
# </div>
|
33
33
|
#
|
34
|
-
# @example
|
34
|
+
# @example Wrapping another component
|
35
35
|
# <div class="pt-5">
|
36
36
|
# <%= render(Primer::TooltipComponent.new(label: "Even bolder")) do %>
|
37
37
|
# <%= render(Primer::ButtonComponent.new) { "Bold Button" } %>
|
38
38
|
# <% end %>
|
39
39
|
# </div>
|
40
40
|
#
|
41
|
-
# @example
|
41
|
+
# @example With a direction
|
42
42
|
# <div class="pt-5">
|
43
43
|
# <%= render(Primer::TooltipComponent.new(label: "Even bolder", direction: :s)) { "Bold Text With a Direction" } %>
|
44
44
|
# </div>
|
45
45
|
#
|
46
|
-
# @example
|
46
|
+
# @example With an alignment
|
47
47
|
# <div class="pt-5">
|
48
48
|
# <%= render(Primer::TooltipComponent.new(label: "Even bolder", direction: :s, alignment: :right_1)) { "Bold Text With an Alignment" } %>
|
49
49
|
# </div>
|
50
50
|
#
|
51
|
-
# @example
|
51
|
+
# @example Without a delay
|
52
52
|
# <div class="pt-5">
|
53
53
|
# <%= render(Primer::TooltipComponent.new(label: "Even bolder", direction: :s, no_delay: true)) { "Bold Text without a delay" } %>
|
54
54
|
# </div>
|
@@ -3,18 +3,18 @@
|
|
3
3
|
module Primer
|
4
4
|
# Use TruncateComponent to shorten overflowing text with an ellipsis.
|
5
5
|
class TruncateComponent < Primer::Component
|
6
|
-
# @example
|
6
|
+
# @example Default
|
7
7
|
# <div class="col-2">
|
8
8
|
# <%= render(Primer::TruncateComponent.new(tag: :p)) { "branch-name-that-is-really-long" } %>
|
9
9
|
# </div>
|
10
10
|
#
|
11
|
-
# @example
|
11
|
+
# @example Inline
|
12
12
|
# <%= render(Primer::TruncateComponent.new(tag: :span, inline: true)) { "branch-name-that-is-really-long" } %>
|
13
13
|
#
|
14
|
-
# @example
|
14
|
+
# @example Expandable
|
15
15
|
# <%= render(Primer::TruncateComponent.new(tag: :span, inline: true, expandable: true)) { "branch-name-that-is-really-long" } %>
|
16
16
|
#
|
17
|
-
# @example
|
17
|
+
# @example Custom size
|
18
18
|
# <%= render(Primer::TruncateComponent.new(tag: :span, inline: true, expandable: true, max_width: 100)) { "branch-name-that-is-really-long" } %>
|
19
19
|
#
|
20
20
|
# @param inline [Boolean] Whether the element is inline (or inline-block).
|
@@ -10,7 +10,7 @@ module Primer
|
|
10
10
|
|
11
11
|
with_content_areas :body, :actions
|
12
12
|
|
13
|
-
# @example
|
13
|
+
# @example Default
|
14
14
|
# <%= render(Primer::UnderlineNavComponent.new) do |component| %>
|
15
15
|
# <% component.with(:body) do %>
|
16
16
|
# <%= render(Primer::LinkComponent.new(href: "#url")) { "Item 1" } %>
|
@@ -20,7 +20,7 @@ module Primer
|
|
20
20
|
# <% end %>
|
21
21
|
# <% end %>
|
22
22
|
#
|
23
|
-
# @example
|
23
|
+
# @example Align right
|
24
24
|
# <%= render(Primer::UnderlineNavComponent.new(align: :right)) do |component| %>
|
25
25
|
# <% component.with(:body) do %>
|
26
26
|
# <%= render(Primer::LinkComponent.new(href: "#url")) { "Item 1" } %>
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Primer
|
4
|
+
# Module to allow shorthand calls for registered Primer components
|
5
|
+
#
|
6
|
+
# Registered components can be called with
|
7
|
+
# `primer(:name, **kwargs) { block }` instead of
|
8
|
+
# `render Primer::NameComponent.new(**kwargs) { block }`
|
9
|
+
module ViewHelper
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
class ViewHelperNotFound < StandardError; end
|
13
|
+
|
14
|
+
def primer(name, **component_args, &block)
|
15
|
+
component = Primer::Component.primer_helpers[name]
|
16
|
+
|
17
|
+
raise ViewHelperNotFound, "no component defined for helper #{name}" if component.blank?
|
18
|
+
|
19
|
+
render component.new(**component_args), &block
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Primer
|
6
|
+
# :nodoc:
|
7
|
+
module ViewHelper
|
8
|
+
# DSL to allow components to register a View Helper for shorthand calls.
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# class MyComponent < ViewComponent::Base
|
13
|
+
# include Primer::ViewHelper::DSL
|
14
|
+
# view_helper :my_component
|
15
|
+
# end
|
16
|
+
module DSL
|
17
|
+
extend ActiveSupport::Concern
|
18
|
+
|
19
|
+
class ViewHelperAlreadyDefined < StandardError; end
|
20
|
+
|
21
|
+
included do
|
22
|
+
class_attribute :primer_helpers, instance_writer: false, default: {}
|
23
|
+
end
|
24
|
+
|
25
|
+
class_methods do
|
26
|
+
def view_helper(name)
|
27
|
+
raise ViewHelperAlreadyDefined, "#{name} is already defined" if primer_helpers[name].present?
|
28
|
+
|
29
|
+
primer_helpers[name] = self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|