ui_components 0.1.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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +43 -0
  6. data/Rakefile +4 -0
  7. data/app/assets/javascripts/ui_components/controllers/clipboard_controller.js +22 -0
  8. data/app/components/documentation/body/example_preview_component.rb +24 -0
  9. data/app/components/documentation/body_component.html.erb +26 -0
  10. data/app/components/documentation/body_component.rb +41 -0
  11. data/app/components/documentation_component.rb +20 -0
  12. data/app/components/ui_components/daisy_ui/action/button_component.rb +83 -0
  13. data/app/components/ui_components/daisy_ui/action/dropdown/anchor_component.rb +35 -0
  14. data/app/components/ui_components/daisy_ui/action/dropdown/card_component.rb +41 -0
  15. data/app/components/ui_components/daisy_ui/action/dropdown/card_item_component.rb +29 -0
  16. data/app/components/ui_components/daisy_ui/action/dropdown/list_item_component.rb +30 -0
  17. data/app/components/ui_components/daisy_ui/action/dropdown/menu_component.rb +40 -0
  18. data/app/components/ui_components/daisy_ui/action/dropdown_component.rb +79 -0
  19. data/app/components/ui_components/daisy_ui/action/modal/anchor_component.rb +43 -0
  20. data/app/components/ui_components/daisy_ui/action/modal/dialog/box/action/item_component.rb +33 -0
  21. data/app/components/ui_components/daisy_ui/action/modal/dialog/box/action_component.rb +42 -0
  22. data/app/components/ui_components/daisy_ui/action/modal/dialog/box/body_component.rb +31 -0
  23. data/app/components/ui_components/daisy_ui/action/modal/dialog/box/close_component.rb +33 -0
  24. data/app/components/ui_components/daisy_ui/action/modal/dialog/box/title_component.rb +31 -0
  25. data/app/components/ui_components/daisy_ui/action/modal/dialog/box_component.rb +54 -0
  26. data/app/components/ui_components/daisy_ui/action/modal/dialog_component.rb +58 -0
  27. data/app/components/ui_components/daisy_ui/action/modal_component.rb +33 -0
  28. data/app/components/ui_components/daisy_ui/action/swap/off_component.rb +27 -0
  29. data/app/components/ui_components/daisy_ui/action/swap/on_component.rb +27 -0
  30. data/app/components/ui_components/daisy_ui/action/swap_component.rb +59 -0
  31. data/app/components/ui_components/daisy_ui/base_component.rb +41 -0
  32. data/app/components/ui_components/daisy_ui/data_display/accordion/anchor_component.rb +27 -0
  33. data/app/components/ui_components/daisy_ui/data_display/accordion/body_component.rb +27 -0
  34. data/app/components/ui_components/daisy_ui/data_display/accordion/item_component.rb +69 -0
  35. data/app/components/ui_components/daisy_ui/data_display/accordion_component.rb +32 -0
  36. data/app/components/ui_components/daisy_ui/data_display/avatar/box_component.rb +61 -0
  37. data/app/components/ui_components/daisy_ui/data_display/avatar/item_component.rb +62 -0
  38. data/app/components/ui_components/daisy_ui/data_display/avatar_component.rb +21 -0
  39. data/app/components/ui_components/daisy_ui/data_display/badge_component.rb +58 -0
  40. data/app/controllers/ui_components/daisy_ui/action_components_controller.rb +15 -0
  41. data/app/controllers/ui_components/daisy_ui/application_controller.rb +9 -0
  42. data/app/controllers/ui_components/daisy_ui/data_display_components_controller.rb +13 -0
  43. data/app/controllers/ui_components/daisy_ui/home_controller.rb +9 -0
  44. data/app/helpers/action/button_helper.rb +18 -0
  45. data/app/helpers/action/dropdown_helper.rb +19 -0
  46. data/app/helpers/data_display/badge_helper.rb +26 -0
  47. data/app/views/ui_components/daisy_ui/action_components/button.html.erb +114 -0
  48. data/app/views/ui_components/daisy_ui/action_components/dropdown.html.erb +117 -0
  49. data/app/views/ui_components/daisy_ui/action_components/modal.html.erb +73 -0
  50. data/app/views/ui_components/daisy_ui/action_components/swap.html.erb +97 -0
  51. data/app/views/ui_components/daisy_ui/data_display_components/accordion.html.erb +205 -0
  52. data/app/views/ui_components/daisy_ui/data_display_components/avatar.html.erb +261 -0
  53. data/app/views/ui_components/daisy_ui/data_display_components/badge.html.erb +150 -0
  54. data/app/views/ui_components/daisy_ui/home/index.html.erb +20 -0
  55. data/config/routes.rb +22 -0
  56. data/lib/tasks/ui_components_tasks.rake +55 -0
  57. data/lib/ui_components/config/daisy_ui_class_names.rb +33 -0
  58. data/lib/ui_components/config/tailwind_class_names.rb +44 -0
  59. data/lib/ui_components/config.rb +52 -0
  60. data/lib/ui_components/engine.rb +12 -0
  61. data/lib/ui_components/version.rb +5 -0
  62. data/lib/ui_components.rb +9 -0
  63. data/sig/ui_components.rbs +4 -0
  64. metadata +155 -0
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ class DropdownComponent < BaseComponent
7
+ CSS_CLASSES_DEFAULT = %w[dropdown]
8
+ CSS_CLASSES_VARIANTS = (
9
+ %w[dropdown-hover dropdown-open] +
10
+ ALIGNS.map { |color| "dropdown-#{color}" } +
11
+ AXES.map { |size| "dropdown-#{size}" }
12
+ ).freeze
13
+
14
+ CSS_CLASSES = (
15
+ CSS_CLASSES_DEFAULT +
16
+ CSS_CLASSES_VARIANTS +
17
+ Dropdown::AnchorComponent::CSS_CLASSES +
18
+ Dropdown::MenuComponent::CSS_CLASSES +
19
+ Dropdown::CardComponent::CSS_CLASSES
20
+ ).freeze
21
+
22
+ erb_template <<-ERB
23
+ <%= tag.div(**set_options) do %>
24
+ <%= anchor %>
25
+ <%= menu&.content.present? ? menu : card %>
26
+ <% end %>
27
+ ERB
28
+
29
+ renders_one :anchor, Dropdown::AnchorComponent
30
+ renders_one :menu, Dropdown::MenuComponent
31
+ renders_one :card, Dropdown::CardComponent
32
+
33
+ def initialize(
34
+ align: nil,
35
+ axis: nil,
36
+ hover: false,
37
+ opened: false,
38
+ **args
39
+ )
40
+ @align = align
41
+ @axis = axis
42
+ @hover = hover
43
+ @opened = opened
44
+
45
+ super(**args)
46
+ end
47
+
48
+ private
49
+
50
+ def class_string
51
+ TailwindMerge::Merger.new.merge([
52
+ *CSS_CLASSES_DEFAULT,
53
+ align_class,
54
+ axis_class,
55
+ hover_class,
56
+ opened_class,
57
+ @css_class
58
+ ])
59
+ end
60
+
61
+ def align_class
62
+ "dropdown-#{@align}" if ALIGNS.include?(@align)
63
+ end
64
+
65
+ def axis_class
66
+ "dropdown-#{@axis}" if AXES.include?(@axis)
67
+ end
68
+
69
+ def hover_class
70
+ "dropdown-hover" if @hover
71
+ end
72
+
73
+ def opened_class
74
+ "dropdown-open" if @opened
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ module Modal
7
+ class AnchorComponent < BaseComponent
8
+ CSS_CLASSES_DEFAULT = %w[btn].freeze
9
+ CSS_CLASSES = CSS_CLASSES_DEFAULT
10
+
11
+ erb_template <<-ERB
12
+ <%= tag.button(**set_options) { content } %>
13
+ ERB
14
+
15
+ def initialize(
16
+ opener:,
17
+ **args
18
+ )
19
+ @opener = opener
20
+
21
+ super(**args)
22
+ end
23
+
24
+ private
25
+
26
+ def set_options
27
+ @options.merge!(class: class_string)
28
+ @options.merge!(style: @css_style) if @css_style.present?
29
+ @options.merge!(onclick: "#{@opener}.showModal()")
30
+ @options.compact_blank!
31
+ end
32
+
33
+ def class_string
34
+ TailwindMerge::Merger.new.merge([
35
+ *CSS_CLASSES_DEFAULT,
36
+ @css_class
37
+ ])
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ module Modal
7
+ module Dialog
8
+ module Box
9
+ module Action
10
+ class ItemComponent < BaseComponent
11
+ CSS_CLASSES_DEFAULT = %w[btn ml-1].freeze
12
+ CSS_CLASSES = CSS_CLASSES_DEFAULT
13
+
14
+ erb_template <<-ERB
15
+ <%= tag.button(**set_options) { content } %>
16
+ ERB
17
+
18
+ private
19
+
20
+ def class_string
21
+ TailwindMerge::Merger.new.merge([
22
+ *CSS_CLASSES_DEFAULT,
23
+ @css_class
24
+ ])
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ module Modal
7
+ module Dialog
8
+ module Box
9
+ class ActionComponent < BaseComponent
10
+ CSS_CLASSES_DEFAULT = %w[modal-action].freeze
11
+ CSS_CLASSES = (
12
+ CSS_CLASSES_DEFAULT +
13
+ Action::ItemComponent::CSS_CLASSES
14
+ )
15
+
16
+ erb_template <<-ERB
17
+ <%= tag.div(**set_options) do %>
18
+ <form method="dialog">
19
+ <% items.each do |item| %>
20
+ <%= item %>
21
+ <% end %>
22
+ </form>
23
+ <% end %>
24
+ ERB
25
+
26
+ renders_many :items, Action::ItemComponent
27
+
28
+ private
29
+
30
+ def class_string
31
+ TailwindMerge::Merger.new.merge([
32
+ *CSS_CLASSES_DEFAULT,
33
+ @css_class
34
+ ])
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ module Modal
7
+ module Dialog
8
+ module Box
9
+ class BodyComponent < BaseComponent
10
+ CSS_CLASSES_DEFAULT = %w[py-4].freeze
11
+ CSS_CLASSES = CSS_CLASSES_DEFAULT
12
+
13
+ erb_template <<-ERB
14
+ <%= tag.div(**set_options) { content } %>
15
+ ERB
16
+
17
+ private
18
+
19
+ def class_string
20
+ TailwindMerge::Merger.new.merge([
21
+ *CSS_CLASSES_DEFAULT,
22
+ @css_class
23
+ ])
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ module Modal
7
+ module Dialog
8
+ module Box
9
+ class CloseComponent < BaseComponent
10
+ CSS_CLASSES_DEFAULT = %w[btn btn-sm btn-circle btn-ghost absolute right-2 top-2].freeze
11
+ CSS_CLASSES = CSS_CLASSES_DEFAULT
12
+
13
+ erb_template <<-ERB
14
+ <form method="dialog">
15
+ <%= tag.button(**set_options) { content } %>
16
+ </form>
17
+ ERB
18
+
19
+ private
20
+
21
+ def class_string
22
+ TailwindMerge::Merger.new.merge([
23
+ *CSS_CLASSES_DEFAULT,
24
+ @css_class
25
+ ])
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ module Modal
7
+ module Dialog
8
+ module Box
9
+ class TitleComponent < BaseComponent
10
+ CSS_CLASSES_DEFAULT = %w[text-lg font-bold].freeze
11
+ CSS_CLASSES = CSS_CLASSES_DEFAULT
12
+
13
+ erb_template <<-ERB
14
+ <%= tag.h3(**set_options) { content } %>
15
+ ERB
16
+
17
+ private
18
+
19
+ def class_string
20
+ TailwindMerge::Merger.new.merge([
21
+ *CSS_CLASSES_DEFAULT,
22
+ @css_class
23
+ ])
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ module Modal
7
+ module Dialog
8
+ class BoxComponent < BaseComponent
9
+ CSS_CLASSES_DEFAULT = %w[modal-box].freeze
10
+ CSS_CLASSES = (
11
+ CSS_CLASSES_DEFAULT +
12
+ Box::TitleComponent::CSS_CLASSES +
13
+ Box::BodyComponent::CSS_CLASSES +
14
+ Box::ActionComponent::CSS_CLASSES +
15
+ Box::CloseComponent::CSS_CLASSES
16
+ ).freeze
17
+
18
+ erb_template <<-ERB
19
+ <%= tag.div(**set_options) do %>
20
+ <%= close if close&.content.present? %>
21
+ <%= title %>
22
+ <%= body %>
23
+ <%= action unless @backdrop || close&.content.present? %>
24
+ <% end %>
25
+ ERB
26
+
27
+ renders_one :title, Box::TitleComponent
28
+ renders_one :body, Box::BodyComponent
29
+ renders_one :action, Box::ActionComponent
30
+ renders_one :close, Box::CloseComponent
31
+
32
+ def initialize(
33
+ backdrop: false,
34
+ **args
35
+ )
36
+ @backdrop = backdrop
37
+
38
+ super(**args)
39
+ end
40
+
41
+ private
42
+
43
+ def class_string
44
+ TailwindMerge::Merger.new.merge([
45
+ *CSS_CLASSES_DEFAULT,
46
+ @css_class
47
+ ])
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ module Modal
7
+ class DialogComponent < BaseComponent
8
+ CSS_CLASSES_DEFAULT = %w[modal].freeze
9
+ CSS_CLASSES = (
10
+ %w[modal-backdrop] +
11
+ CSS_CLASSES_DEFAULT +
12
+ Dialog::BoxComponent::CSS_CLASSES
13
+ ).freeze
14
+
15
+ erb_template <<-ERB
16
+ <%= tag.dialog(**set_options) do %>
17
+ <%= box %>
18
+ <% if @backdrop %>
19
+ <form method="dialog" class="modal-backdrop">
20
+ <button>close</button>
21
+ </form>
22
+ <% end %>
23
+ <% end %>
24
+ ERB
25
+
26
+ renders_one :box, ->(**args) { Dialog::BoxComponent.new(**args.merge(backdrop: @backdrop)) }
27
+
28
+ def initialize(
29
+ backdrop: false,
30
+ opener:,
31
+ **args
32
+ )
33
+ @backdrop = backdrop
34
+ @opener = opener
35
+
36
+ super(**args)
37
+ end
38
+
39
+ def set_options
40
+ @options.merge!(class: class_string)
41
+ @options.merge!(style: @css_style) if @css_style.present?
42
+ @options.merge!(id: @opener)
43
+ @options.compact_blank!
44
+ end
45
+
46
+ private
47
+
48
+ def class_string
49
+ TailwindMerge::Merger.new.merge([
50
+ *CSS_CLASSES_DEFAULT,
51
+ @css_class
52
+ ])
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ class ModalComponent < BaseComponent
7
+ CSS_CLASSES = (
8
+ Modal::AnchorComponent::CSS_CLASSES +
9
+ Modal::DialogComponent::CSS_CLASSES
10
+ ).freeze
11
+
12
+ erb_template <<-ERB
13
+ <%= tag.div(**set_options) do %>
14
+ <%= anchor %>
15
+ <%= dialog %>
16
+ <% end %>
17
+ ERB
18
+
19
+ renders_one :anchor, ->(**args) { Modal::AnchorComponent.new(**args.merge(opener: @opener)) }
20
+ renders_one :dialog, ->(**args) { Modal::DialogComponent.new(**args.merge(opener: @opener)) }
21
+
22
+ def initialize(
23
+ opener: "my_modal",
24
+ **args
25
+ )
26
+ @opener = opener
27
+
28
+ super(**args)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ module Swap
7
+ class OffComponent < BaseComponent
8
+ CSS_CLASSES_DEFAULT = %w[swap-off].freeze
9
+ CSS_CLASSES = CSS_CLASSES_DEFAULT
10
+
11
+ erb_template <<-ERB
12
+ <%= tag.div(**set_options) { content } %>
13
+ ERB
14
+
15
+ private
16
+
17
+ def class_string
18
+ TailwindMerge::Merger.new.merge([
19
+ *CSS_CLASSES_DEFAULT,
20
+ @css_class
21
+ ])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ module Swap
7
+ class OnComponent < BaseComponent
8
+ CSS_CLASSES_DEFAULT = %w[swap-on].freeze
9
+ CSS_CLASSES = CSS_CLASSES_DEFAULT
10
+
11
+ erb_template <<-ERB
12
+ <%= tag.div(**set_options) { content } %>
13
+ ERB
14
+
15
+ private
16
+
17
+ def class_string
18
+ TailwindMerge::Merger.new.merge([
19
+ *CSS_CLASSES_DEFAULT,
20
+ @css_class
21
+ ])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module Action
6
+ class SwapComponent < BaseComponent
7
+ EFFECTS = %w[rotate flip]
8
+
9
+ CSS_CLASSES_DEFAULT = %w[swap].freeze
10
+ CSS_CLASSES_VARIANTS = (
11
+ %w[swap-active] +
12
+ EFFECTS.map { |key| "swap-#{key}" }
13
+ ).freeze
14
+ CSS_CLASSES = (CSS_CLASSES_DEFAULT + CSS_CLASSES_VARIANTS).freeze
15
+
16
+ erb_template <<-ERB
17
+ <%= tag.label(**set_options) do %>
18
+ <input type="checkbox" />
19
+ <%= on %>
20
+ <%= off %>
21
+ <% end %>
22
+ ERB
23
+
24
+ renders_one :on, Swap::OnComponent
25
+ renders_one :off, Swap::OffComponent
26
+
27
+ def initialize(
28
+ effect: nil,
29
+ active: false,
30
+ **args
31
+ )
32
+ @effect = effect
33
+ @active = active
34
+
35
+ super(**args)
36
+ end
37
+
38
+ private
39
+
40
+ def class_string
41
+ TailwindMerge::Merger.new.merge([
42
+ *CSS_CLASSES_DEFAULT,
43
+ effect_class,
44
+ active_class,
45
+ @css_class
46
+ ])
47
+ end
48
+
49
+ def effect_class
50
+ "swap-#{@effect}" if EFFECTS.include?(@effect)
51
+ end
52
+
53
+ def active_class
54
+ "swap-active" if @active
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ class BaseComponent < ViewComponent::Base
6
+ require "tailwind_merge"
7
+
8
+ SIZES = %w[xs sm lg xl].freeze
9
+ KINDS = %w[soft outline dash active disabled ghost link].freeze
10
+ COLORS = %w[neutral primary secondary accent info success warning error].freeze
11
+ ALIGNS = %w[start center end].freeze
12
+ AXES = %w[top right bottom left].freeze
13
+
14
+ CSS_CLASSES_DEFAULT = %w[].freeze
15
+ CSS_CLASSES_VARIANTS = %w[].freeze
16
+ CSS_CLASSES = (CSS_CLASSES_DEFAULT + CSS_CLASSES_VARIANTS).freeze
17
+
18
+ def initialize(
19
+ css_class: nil,
20
+ css_style: nil,
21
+ options: {}
22
+ )
23
+ @css_class = css_class.to_s
24
+ @css_style = css_style.to_s
25
+ @options = options
26
+ end
27
+
28
+ private
29
+
30
+ def set_options
31
+ @options.merge!(class: class_string)
32
+ @options.merge!(style: @css_style) if @css_style.present?
33
+ @options.compact_blank!
34
+ end
35
+
36
+ def class_string
37
+ @css_class
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module DataDisplay
6
+ module Accordion
7
+ class AnchorComponent < BaseComponent
8
+ CSS_CLASSES_DEFAULT = %w[collapse-title].freeze
9
+ CSS_CLASSES = CSS_CLASSES_DEFAULT
10
+
11
+ erb_template <<-ERB
12
+ <%= tag.div(**set_options) { content } %>
13
+ ERB
14
+
15
+ private
16
+
17
+ def class_string
18
+ TailwindMerge::Merger.new.merge([
19
+ *CSS_CLASSES_DEFAULT,
20
+ @css_class
21
+ ])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UiComponents
4
+ module DaisyUi
5
+ module DataDisplay
6
+ module Accordion
7
+ class BodyComponent < BaseComponent
8
+ CSS_CLASSES_DEFAULT = %w[collapse-content].freeze
9
+ CSS_CLASSES = CSS_CLASSES_DEFAULT
10
+
11
+ erb_template <<-ERB
12
+ <%= tag.div(**set_options) { content } %>
13
+ ERB
14
+
15
+ private
16
+
17
+ def class_string
18
+ TailwindMerge::Merger.new.merge([
19
+ *CSS_CLASSES_DEFAULT,
20
+ @css_class
21
+ ])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end