bs5 0.0.25 → 0.0.30

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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/app/components/bs5/carousel/caption_component.html.erb +3 -0
  3. data/app/components/bs5/carousel/caption_component.rb +8 -0
  4. data/app/components/bs5/carousel/item_component.html.erb +4 -0
  5. data/app/components/bs5/carousel/item_component.rb +74 -0
  6. data/app/components/bs5/carousel_component.html.erb +30 -0
  7. data/app/components/bs5/carousel_component.rb +60 -0
  8. data/app/components/bs5/dropdown/item_component.html.erb +1 -0
  9. data/app/components/bs5/dropdown/item_component.rb +39 -0
  10. data/app/components/bs5/dropdown_component.html.erb +22 -0
  11. data/app/components/bs5/dropdown_component.rb +154 -0
  12. data/app/components/bs5/modal/body_component.html.erb +3 -0
  13. data/app/components/bs5/modal/body_component.rb +8 -0
  14. data/app/components/bs5/modal/controller_component.html.erb +1 -0
  15. data/app/components/bs5/modal/controller_component.rb +40 -0
  16. data/app/components/bs5/modal/footer_component.html.erb +4 -0
  17. data/app/components/bs5/modal/footer_component.rb +8 -0
  18. data/app/components/bs5/modal/header_component.html.erb +4 -0
  19. data/app/components/bs5/modal/header_component.rb +18 -0
  20. data/app/components/bs5/modal_component.html.erb +11 -0
  21. data/app/components/bs5/modal_component.rb +80 -0
  22. data/app/components/bs5/nav/item_component.html.erb +1 -0
  23. data/app/components/bs5/nav/item_component.rb +80 -0
  24. data/app/components/bs5/nav_component.html.erb +7 -0
  25. data/app/components/bs5/nav_component.rb +47 -0
  26. data/app/components/bs5/progress/bar_component.html.erb +1 -0
  27. data/app/components/bs5/progress/bar_component.rb +60 -0
  28. data/app/components/bs5/progress_component.html.erb +9 -0
  29. data/app/components/bs5/progress_component.rb +15 -0
  30. data/app/components/bs5/tabs/item_component.html.erb +3 -0
  31. data/app/components/bs5/tabs/item_component.rb +48 -0
  32. data/app/components/bs5/tabs/link_component.html.erb +1 -0
  33. data/app/components/bs5/tabs/link_component.rb +27 -0
  34. data/app/components/bs5/tabs_component.html.erb +16 -0
  35. data/app/components/bs5/tabs_component.rb +63 -0
  36. data/app/helpers/bs5/components_helper.rb +6 -3
  37. data/app/views/bs5/examples/alert/color/snippet.html.erb +8 -24
  38. data/app/views/bs5/examples/alert/default/snippet.html.erb +1 -3
  39. data/app/views/bs5/examples/carousel/_dark_variant.html.erb +2 -0
  40. data/app/views/bs5/examples/carousel/_examples.html.erb +13 -0
  41. data/app/views/bs5/examples/carousel/dark_variant/snippet1.html.erb +25 -0
  42. data/app/views/bs5/examples/carousel/examples/snippet1.html.erb +13 -0
  43. data/app/views/bs5/examples/carousel/examples/snippet2.html.erb +13 -0
  44. data/app/views/bs5/examples/carousel/examples/snippet3.html.erb +13 -0
  45. data/app/views/bs5/examples/carousel/examples/snippet4.html.erb +25 -0
  46. data/app/views/bs5/examples/carousel/examples/snippet5.html.erb +13 -0
  47. data/app/views/bs5/examples/carousel/examples/snippet6.html.erb +13 -0
  48. data/app/views/bs5/examples/dropdowns/dark/_example.html.erb +2 -0
  49. data/app/views/bs5/examples/dropdowns/dark/snippet.html.erb +7 -0
  50. data/app/views/bs5/examples/dropdowns/directions/_example.html.erb +7 -0
  51. data/app/views/bs5/examples/dropdowns/directions/snippet1.html.erb +17 -0
  52. data/app/views/bs5/examples/dropdowns/directions/snippet2.html.erb +17 -0
  53. data/app/views/bs5/examples/dropdowns/directions/snippet3.html.erb +17 -0
  54. data/app/views/bs5/examples/dropdowns/menu_alignment/_example.html.erb +5 -0
  55. data/app/views/bs5/examples/dropdowns/menu_alignment/snippet1.html.erb +5 -0
  56. data/app/views/bs5/examples/dropdowns/menu_alignment/snippet2.html.erb +7 -0
  57. data/app/views/bs5/examples/dropdowns/menu_alignment/snippet3.html.erb +7 -0
  58. data/app/views/bs5/examples/dropdowns/menu_content/_example.html.erb +9 -0
  59. data/app/views/bs5/examples/dropdowns/menu_content/snippet1.html.erb +5 -0
  60. data/app/views/bs5/examples/dropdowns/menu_content/snippet2.html.erb +7 -0
  61. data/app/views/bs5/examples/dropdowns/menu_content/snippet3.html.erb +10 -0
  62. data/app/views/bs5/examples/dropdowns/menu_content/snippet4.html.erb +24 -0
  63. data/app/views/bs5/examples/dropdowns/menu_items/_example.html.erb +7 -0
  64. data/app/views/bs5/examples/dropdowns/menu_items/snippet1.html.erb +5 -0
  65. data/app/views/bs5/examples/dropdowns/menu_items/snippet2.html.erb +6 -0
  66. data/app/views/bs5/examples/dropdowns/menu_items/snippet3.html.erb +5 -0
  67. data/app/views/bs5/examples/dropdowns/menu_items/snippet4.html.erb +5 -0
  68. data/app/views/bs5/examples/dropdowns/single/_example.html.erb +4 -0
  69. data/app/views/bs5/examples/dropdowns/single/snippet1.html.erb +5 -0
  70. data/app/views/bs5/examples/dropdowns/single/snippet2.html.erb +47 -0
  71. data/app/views/bs5/examples/dropdowns/single/snippet3.html.erb +47 -0
  72. data/app/views/bs5/examples/dropdowns/sizing/_example.html.erb +3 -0
  73. data/app/views/bs5/examples/dropdowns/sizing/snippet1.html.erb +17 -0
  74. data/app/views/bs5/examples/dropdowns/sizing/snippet2.html.erb +17 -0
  75. data/app/views/bs5/examples/dropdowns/split/_example.html.erb +3 -0
  76. data/app/views/bs5/examples/dropdowns/split/snippet1.html.erb +35 -0
  77. data/app/views/bs5/examples/dropdowns/split/snippet2.html.erb +47 -0
  78. data/app/views/bs5/examples/list_group/active/snippet.html.erb +5 -5
  79. data/app/views/bs5/examples/list_group/default/snippet.html.erb +5 -5
  80. data/app/views/bs5/examples/list_group/disabled/snippet.html.erb +5 -5
  81. data/app/views/bs5/examples/list_group/flush/snippet.html.erb +5 -5
  82. data/app/views/bs5/examples/list_group/horizontal/snippet.html.erb +18 -18
  83. data/app/views/bs5/examples/list_group/style/default.html.erb +8 -8
  84. data/app/views/bs5/examples/modal/_examples.html.erb +9 -0
  85. data/app/views/bs5/examples/modal/_fullscreen.html.erb +2 -0
  86. data/app/views/bs5/examples/modal/_optional_sizes.html.erb +2 -0
  87. data/app/views/bs5/examples/modal/examples/snippet1.html.erb +12 -0
  88. data/app/views/bs5/examples/modal/examples/snippet2.html.erb +12 -0
  89. data/app/views/bs5/examples/modal/examples/snippet3.html.erb +14 -0
  90. data/app/views/bs5/examples/modal/examples/snippet4.html.erb +12 -0
  91. data/app/views/bs5/examples/modal/fullscreen/snippet1.html.erb +55 -0
  92. data/app/views/bs5/examples/modal/optional_sizes/snippet1.html.erb +23 -0
  93. data/app/views/bs5/examples/navs_and_tabs/_base_nav.html.erb +2 -0
  94. data/app/views/bs5/examples/navs_and_tabs/_dropdowns.html.erb +5 -0
  95. data/app/views/bs5/examples/navs_and_tabs/_flex_utils.html.erb +2 -0
  96. data/app/views/bs5/examples/navs_and_tabs/_styles.html.erb +15 -0
  97. data/app/views/bs5/examples/navs_and_tabs/_tabs.html.erb +4 -0
  98. data/app/views/bs5/examples/navs_and_tabs/base_nav/snippet1.html.erb +6 -0
  99. data/app/views/bs5/examples/navs_and_tabs/dropdowns/snippet1.html.erb +14 -0
  100. data/app/views/bs5/examples/navs_and_tabs/dropdowns/snippet2.html.erb +14 -0
  101. data/app/views/bs5/examples/navs_and_tabs/flex_utils/snippet1.html.erb +6 -0
  102. data/app/views/bs5/examples/navs_and_tabs/styles/snippet1.html.erb +6 -0
  103. data/app/views/bs5/examples/navs_and_tabs/styles/snippet2.html.erb +6 -0
  104. data/app/views/bs5/examples/navs_and_tabs/styles/snippet3.html.erb +6 -0
  105. data/app/views/bs5/examples/navs_and_tabs/styles/snippet4.html.erb +6 -0
  106. data/app/views/bs5/examples/navs_and_tabs/styles/snippet5.html.erb +6 -0
  107. data/app/views/bs5/examples/navs_and_tabs/styles/snippet6.html.erb +6 -0
  108. data/app/views/bs5/examples/navs_and_tabs/styles/snippet7.html.erb +6 -0
  109. data/app/views/bs5/examples/navs_and_tabs/styles/snippet8.html.erb +6 -0
  110. data/app/views/bs5/examples/navs_and_tabs/styles/snippet9.html.erb +6 -0
  111. data/app/views/bs5/examples/navs_and_tabs/tabs/snippet1.html.erb +11 -0
  112. data/app/views/bs5/examples/navs_and_tabs/tabs/snippet2.html.erb +11 -0
  113. data/app/views/bs5/examples/navs_and_tabs/tabs/snippet3.html.erb +11 -0
  114. data/app/views/bs5/examples/progress/_animated.html.erb +2 -0
  115. data/app/views/bs5/examples/progress/_backgrounds.html.erb +2 -0
  116. data/app/views/bs5/examples/progress/_examples.html.erb +2 -0
  117. data/app/views/bs5/examples/progress/_labels.html.erb +2 -0
  118. data/app/views/bs5/examples/progress/_multiple_bars.html.erb +2 -0
  119. data/app/views/bs5/examples/progress/_striped.html.erb +2 -0
  120. data/app/views/bs5/examples/progress/animated/snippet1.html.erb +13 -0
  121. data/app/views/bs5/examples/progress/backgrounds/snippet1.html.erb +13 -0
  122. data/app/views/bs5/examples/progress/examples/snippet1.html.erb +17 -0
  123. data/app/views/bs5/examples/progress/labels/snippet1.html.erb +5 -0
  124. data/app/views/bs5/examples/progress/multiple_bars/snippet1.html.erb +5 -0
  125. data/app/views/bs5/examples/progress/striped/snippet1.html.erb +13 -0
  126. data/app/views/bs5/examples/toasts/default/snippet.html.erb +1 -1
  127. data/app/views/bs5/pages/carousel.html.erb +3 -0
  128. data/app/views/bs5/pages/dropdowns.html.erb +10 -0
  129. data/app/views/bs5/pages/modal.html.erb +4 -0
  130. data/app/views/bs5/pages/navs_and_tabs.html.erb +6 -0
  131. data/app/views/bs5/pages/progress.html.erb +7 -0
  132. data/app/views/layouts/bs5/pages.html.erb +5 -0
  133. data/lib/bs5/version.rb +1 -1
  134. metadata +122 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93ea7a16c5cb16edd48cabe938083b466af2171b895c9bfb7ca3da0e7bd9cf6a
4
- data.tar.gz: 35bb524562a071eaf46e05da49b951e1606b27524f6e242d10be7a5cb6a4eeca
3
+ metadata.gz: 3cddc2280bdce3dfc49c8c980c0176cbdf80f0f5ad5ecf462482d3514818a7a9
4
+ data.tar.gz: 3b9c82cf6f9eeabb0752a9ab8f5d2c7019b339cc86dcd389d844d4eccb04c65d
5
5
  SHA512:
6
- metadata.gz: 011e84f5073b6cd7c6d9b3d2de76f077fedaeaa87f0e8c2aba6efa48457ffe271e8f3d589ec2b2d59eacccdf0a088e968921f3db682c1a846acb94e6ea7c2815
7
- data.tar.gz: 66f86657bfd99ce53af4ea76123b72eed77454ed885b8c033a16fe6e579b67d20be9aa187a982687c80a80e49921577e107b249d081a8fa538b6bdbbef7080fc
6
+ metadata.gz: c55b185ac6ee246cb9f2b0e853f13238b2f8c90962e2472c69a89d599d9cf003b3a4224718b3dd2d1991e106f7344c214ec9a68eb799101892b115c703fb41e8
7
+ data.tar.gz: b7bd776ed2d0701f63672cb1a4242c5d7e4a9f4f14ff597b9ef79bf9eb88e2f44098b6ce43c573ae3cd8c3601890a3aeed8a4e9c5960c8c5706ec2fc0f9c2378
@@ -0,0 +1,3 @@
1
+ <div class="carousel-caption d-none d-md-block">
2
+ <%= content %>
3
+ </div>
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bs5
4
+ module Carousel
5
+ class CaptionComponent < ViewComponent::Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ <%= tag.div(component_attributes) do %>
2
+ <%= content %>
3
+ <%= caption %>
4
+ <% end %>
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bs5
4
+ module Carousel
5
+ class ItemComponent < ViewComponent::Base
6
+ include ViewComponent::SlotableV2
7
+ using HashRefinement
8
+
9
+ COMPONENT_OPTIONS = %i[interval].freeze
10
+
11
+ renders_one :caption, Bs5::Carousel::CaptionComponent
12
+
13
+ attr_accessor :active
14
+
15
+ def initialize(options = {})
16
+ @options = options.symbolize_keys
17
+ extract_options
18
+ end
19
+
20
+ def active?
21
+ active
22
+ end
23
+
24
+ private
25
+
26
+ def extract_options
27
+ extract_component_options
28
+ end
29
+
30
+ def extract_component_options
31
+ @component_options = @options.extract!(*COMPONENT_OPTIONS)
32
+ end
33
+
34
+ def component_attributes
35
+ { class: component_classes,
36
+ data: @component_options.prefix_keys_with_bs }
37
+ end
38
+
39
+ def component_classes
40
+ class_names = %w[carousel-item]
41
+ class_names << 'active' if active?
42
+
43
+ class_names
44
+ end
45
+
46
+ def content
47
+ set_element_class_names
48
+ element.to_html.html_safe # rubocop:disable Rails/OutputSafety
49
+ end
50
+
51
+ def set_element_class_names
52
+ class_names = Array(element[:class])
53
+ class_names << element_classes
54
+ element[:class] = class_names.join(' ')
55
+ end
56
+
57
+ def element_classes
58
+ %w[d-block w-100]
59
+ end
60
+
61
+ def element
62
+ @element ||= begin
63
+ if (elements = Nokogiri::HTML::DocumentFragment.parse(@content).elements).one?
64
+ elements.first
65
+ end
66
+ end
67
+ end
68
+
69
+ def element?
70
+ !!element
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,30 @@
1
+ <%= tag.div(component_attributes) do %>
2
+ <% items.each_with_index do |item, index| %>
3
+ <% item.active = index.zero? %>
4
+ <% end %>
5
+
6
+ <% if indicators? %>
7
+ <ol class="carousel-indicators">
8
+ <% items.each_with_index do |item, index| %>
9
+ <li data-bs-target="#<%= id %>" data-bs-slide-to="<%= index %>" class="<%= item.active? ? 'active' : '' %>"></li>
10
+ <% end %>
11
+ </ol>
12
+ <% end %>
13
+
14
+ <div class="carousel-inner">
15
+ <% items.each_with_index do |item, index| %>
16
+ <%= item %>
17
+ <% end %>
18
+ </div>
19
+
20
+ <% if controls? %>
21
+ <a class="carousel-control-prev" href="#<%= id %>" role="button" data-bs-slide="prev">
22
+ <span class="carousel-control-prev-icon" aria-hidden="true"></span>
23
+ <span class="visually-hidden">Previous</span>
24
+ </a>
25
+ <a class="carousel-control-next" href="#<%= id %>" role="button" data-bs-slide="next">
26
+ <span class="carousel-control-next-icon" aria-hidden="true"></span>
27
+ <span class="visually-hidden">Next</span>
28
+ </a>
29
+ <% end %>
30
+ <% end %>
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bs5
4
+ class CarouselComponent < ViewComponent::Base
5
+ include ComponentsHelper
6
+ include ViewComponent::SlotableV2
7
+ using HashRefinement
8
+
9
+ CAROUSEL_OPTIONS = %i[interval keyboard pause slide wrap touch].freeze
10
+
11
+ renders_many :items, Bs5::Carousel::ItemComponent
12
+
13
+ def initialize(options = {})
14
+ @options = options.symbolize_keys
15
+ extract_options
16
+ end
17
+
18
+ private
19
+
20
+ def extract_options
21
+ @controls = @options.delete(:controls)
22
+ @indicators = @options.delete(:indicators)
23
+ @crossfade = @options.delete(:crossfade)
24
+ @dark = @options.delete(:dark)
25
+
26
+ extract_carousel_options
27
+ end
28
+
29
+ def extract_carousel_options
30
+ @carousel_options = @options.extract!(*CAROUSEL_OPTIONS)
31
+ end
32
+
33
+ def id
34
+ "carousel-#{object_id}"
35
+ end
36
+
37
+ def items_count
38
+ items.size
39
+ end
40
+
41
+ def component_attributes
42
+ { class: component_class,
43
+ id: id,
44
+ data: @carousel_options.merge(ride: :carousel).prefix_keys_with_bs }
45
+ end
46
+
47
+ def component_class
48
+ class_names = %w[carousel slide]
49
+ class_names << %w[carousel-fade] if crossfade?
50
+ class_names << %w[carousel-dark] if dark?
51
+ class_names.join(' ')
52
+ end
53
+
54
+ %i[controls indicators crossfade dark].each do |name|
55
+ define_method("#{name}?") do
56
+ !instance_variable_get("@#{name}").nil?
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1 @@
1
+ <li><%= content || tag.hr(class: 'dropdown-divider') %></li>
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bs5
4
+ module Dropdown
5
+ class ItemComponent < ViewComponent::Base
6
+ private
7
+
8
+ def content
9
+ return nil if @content.blank?
10
+
11
+ if actionable_element?
12
+ set_actionable_element_class_names
13
+ actionable_element.to_html.html_safe # rubocop:disable Rails/OutputSafety
14
+ else
15
+ @content
16
+ end
17
+ end
18
+
19
+ def set_actionable_element_class_names
20
+ class_names = Array(actionable_element[:class])
21
+ class_names << 'dropdown-item'
22
+ actionable_element[:class] = class_names.join(' ')
23
+ end
24
+
25
+ def actionable_element
26
+ @actionable_element ||= begin
27
+ if (elements = Nokogiri::HTML::DocumentFragment.parse(@content).elements).one? &&
28
+ (element = elements.first).name.in?(%w[a button])
29
+ element
30
+ end
31
+ end
32
+ end
33
+
34
+ def actionable_element?
35
+ !!actionable_element
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ <% if split? %>
2
+ <% if dropstart? %>
3
+ <%= bs5_button_group do %>
4
+ <div class="<%= component_class %>">
5
+ <%= split_button_toggle %>
6
+ <%= menu_content %>
7
+ </div>
8
+ <%= split_button %>
9
+ <% end %>
10
+ <% else %>
11
+ <div class="<%= component_class %>">
12
+ <%= split_button %>
13
+ <%= split_button_toggle %>
14
+ <%= menu_content %>
15
+ </div>
16
+ <% end %>
17
+ <% else %>
18
+ <div class="<%= component_class %>">
19
+ <%= single_button %>
20
+ <%= menu_content %>
21
+ </div>
22
+ <% end %>
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bs5
4
+ class DropdownComponent < ViewComponent::Base # rubocop:disable Metrics/ClassLength
5
+ include ViewComponent::SlotableV2
6
+ include ComponentsHelper
7
+ using HashRefinement
8
+
9
+ CLASS_PREFIX = 'dropdown'
10
+ CLASS_NAMES = {
11
+ visually_hidden: 'visually-hidden',
12
+ toggle_split: "#{CLASS_PREFIX}-toggle-split",
13
+ toggle: "#{CLASS_PREFIX}-toggle",
14
+ menu: "#{CLASS_PREFIX}-menu",
15
+ menu_dark: "#{CLASS_PREFIX}-menu-dark",
16
+ menu_end: "#{CLASS_PREFIX}-menu-end"
17
+ }.freeze
18
+ DIRECTIONS = {
19
+ up: :dropup,
20
+ end: :dropend,
21
+ start: :dropstart
22
+ }.with_indifferent_access.freeze
23
+ DROPDOWN_OPTIONS = %i[offset flip boundary reference display].freeze
24
+
25
+ renders_many :items, Bs5::Dropdown::ItemComponent
26
+ attr_reader :title
27
+
28
+ def initialize(content_or_options = nil, options = {})
29
+ if content_or_options.is_a? Hash
30
+ @options = content_or_options.symbolize_keys
31
+ else
32
+ @title = content_or_options
33
+ @options = options.symbolize_keys
34
+ end
35
+
36
+ extract_options
37
+ end
38
+
39
+ private
40
+
41
+ def extract_options
42
+ @split = @options.delete(:split)
43
+ @dark = @options.delete(:dark)
44
+ @direction = @options.delete(:direction)
45
+ @align = @options.delete(:align)
46
+
47
+ extract_dropdown_options
48
+ end
49
+
50
+ def extract_dropdown_options
51
+ @dropdown_options = @options.extract!(*DROPDOWN_OPTIONS)
52
+ end
53
+
54
+ def split_button_toggle
55
+ bs5_button_tag(split_button_toggle_options) do
56
+ tag.span('Toggle Dropdown', class: CLASS_NAMES[:visually_hidden])
57
+ end
58
+ end
59
+
60
+ def split_button
61
+ bs5_button_tag title, split_button_options
62
+ end
63
+
64
+ def single_button
65
+ bs5_button_tag title, single_button_options
66
+ end
67
+
68
+ def menu_content
69
+ if items.any?
70
+ tag.ul(class: dropdown_menu_classes) { items.map(&method(:concat)) }
71
+ else
72
+ tag.div(content, class: dropdown_menu_classes)
73
+ end
74
+ end
75
+
76
+ def single_button_options
77
+ @options.merge(default_options)
78
+ end
79
+
80
+ def component_class
81
+ class_names = split? ? ['btn-group'] : [CLASS_PREFIX]
82
+ class_names << DIRECTIONS[@direction] if direction?
83
+
84
+ class_names.join(' ')
85
+ end
86
+
87
+ def split_button_options
88
+ @options.merge(default_button_options)
89
+ end
90
+
91
+ def split_button_toggle_options
92
+ single_button_options.dup.tap do |h|
93
+ h[:class] += " #{CLASS_NAMES[:toggle_split]}"
94
+ end
95
+ end
96
+
97
+ %i[split dark direction].each do |name|
98
+ define_method("#{name}?") do
99
+ !instance_variable_get("@#{name}").nil?
100
+ end
101
+ end
102
+
103
+ def dropstart?
104
+ @direction == :start
105
+ end
106
+
107
+ def default_button_options
108
+ { type: :button }
109
+ end
110
+
111
+ def default_options
112
+ default_button_options.merge({
113
+ data: default_data_options.merge(@dropdown_options).prefix_keys_with_bs,
114
+ aria: { expanded: false },
115
+ class: CLASS_NAMES[:toggle]
116
+ })
117
+ end
118
+
119
+ def default_data_options
120
+ { toggle: :dropdown }.tap do |h|
121
+ h[:display] = 'static' if responsive_align?
122
+ end
123
+ end
124
+
125
+ def dropdown_menu_classes
126
+ class_names = [CLASS_NAMES[:menu]]
127
+ class_names << CLASS_NAMES[:menu_dark] if dark?
128
+ class_names << align_classes
129
+
130
+ class_names.compact.join(' ')
131
+ end
132
+
133
+ def align_classes
134
+ case @align
135
+ when Symbol
136
+ CLASS_NAMES[:menu_end]
137
+ when Hash
138
+ responsive_align_classes
139
+ end
140
+ end
141
+
142
+ def responsive_align_classes
143
+ k, v = @align.first
144
+ class_names = ["#{CLASS_PREFIX}-menu-#{v}-#{k}"]
145
+ class_names << CLASS_NAMES[:menu_end] if @align.with_indifferent_access.key?(:start)
146
+
147
+ class_names
148
+ end
149
+
150
+ def responsive_align?
151
+ @align.is_a? Hash
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,3 @@
1
+ <div class="modal-body">
2
+ <%= content %>
3
+ </div>
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bs5
4
+ module Modal
5
+ class BodyComponent < ViewComponent::Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bs5
4
+ module Modal
5
+ class ControllerComponent < ViewComponent::Base
6
+ def initialize(modal_id:)
7
+ @modal_id = modal_id
8
+ end
9
+
10
+ def content
11
+ return nil if @content.blank?
12
+
13
+ if actionable_element?
14
+ set_actionable_element_attributes
15
+ actionable_element.to_html.html_safe # rubocop:disable Rails/OutputSafety
16
+ else
17
+ @content
18
+ end
19
+ end
20
+
21
+ def set_actionable_element_attributes
22
+ actionable_element['data-bs-toggle'] = 'modal'
23
+ actionable_element['data-bs-target'] = "##{@modal_id}"
24
+ end
25
+
26
+ def actionable_element
27
+ @actionable_element ||= begin
28
+ if (elements = Nokogiri::HTML::DocumentFragment.parse(@content).elements).one? &&
29
+ (element = elements.first).name.in?(%w[a button])
30
+ element
31
+ end
32
+ end
33
+ end
34
+
35
+ def actionable_element?
36
+ !!actionable_element
37
+ end
38
+ end
39
+ end
40
+ end