bs5 0.0.23 → 0.0.28

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/app/components/bs5/alert_component.html.erb +1 -1
  3. data/app/components/bs5/alert_component.rb +1 -0
  4. data/app/components/bs5/button_tag_component.rb +10 -1
  5. data/app/components/bs5/carousel/caption_component.html.erb +3 -0
  6. data/app/components/bs5/carousel/caption_component.rb +8 -0
  7. data/app/components/bs5/carousel/item_component.html.erb +4 -0
  8. data/app/components/bs5/carousel/item_component.rb +74 -0
  9. data/app/components/bs5/carousel_component.html.erb +30 -0
  10. data/app/components/bs5/carousel_component.rb +60 -0
  11. data/app/components/bs5/close_button_component.rb +14 -6
  12. data/app/components/bs5/dropdown/item_component.html.erb +1 -0
  13. data/app/components/bs5/dropdown/item_component.rb +39 -0
  14. data/app/components/bs5/dropdown_component.html.erb +22 -0
  15. data/app/components/bs5/dropdown_component.rb +154 -0
  16. data/app/components/bs5/modal/body_component.html.erb +3 -0
  17. data/app/components/bs5/modal/body_component.rb +8 -0
  18. data/app/components/bs5/modal/controller_component.html.erb +1 -0
  19. data/app/components/bs5/modal/controller_component.rb +40 -0
  20. data/app/components/bs5/modal/footer_component.html.erb +4 -0
  21. data/app/components/bs5/modal/footer_component.rb +8 -0
  22. data/app/components/bs5/modal/header_component.html.erb +4 -0
  23. data/app/components/bs5/modal/header_component.rb +18 -0
  24. data/app/components/bs5/modal_component.html.erb +11 -0
  25. data/app/components/bs5/modal_component.rb +80 -0
  26. data/app/components/bs5/toast/body_component.html.erb +3 -0
  27. data/app/components/bs5/toast/body_component.rb +8 -0
  28. data/app/components/bs5/toast/header_component.html.erb +4 -0
  29. data/app/components/bs5/toast/header_component.rb +9 -0
  30. data/app/components/bs5/toast_component.html.erb +8 -0
  31. data/app/components/bs5/toast_component.rb +72 -0
  32. data/app/components/bs5/toast_container_component.html.erb +3 -0
  33. data/app/components/bs5/toast_container_component.rb +19 -0
  34. data/app/helpers/bs5/components_helper.rb +3 -2
  35. data/app/views/bs5/examples/alert/color/snippet.html.erb +8 -24
  36. data/app/views/bs5/examples/alert/default/snippet.html.erb +1 -3
  37. data/app/views/bs5/examples/carousel/_dark_variant.html.erb +2 -0
  38. data/app/views/bs5/examples/carousel/_examples.html.erb +13 -0
  39. data/app/views/bs5/examples/carousel/dark_variant/snippet1.html.erb +25 -0
  40. data/app/views/bs5/examples/carousel/examples/snippet1.html.erb +13 -0
  41. data/app/views/bs5/examples/carousel/examples/snippet2.html.erb +13 -0
  42. data/app/views/bs5/examples/carousel/examples/snippet3.html.erb +13 -0
  43. data/app/views/bs5/examples/carousel/examples/snippet4.html.erb +25 -0
  44. data/app/views/bs5/examples/carousel/examples/snippet5.html.erb +13 -0
  45. data/app/views/bs5/examples/carousel/examples/snippet6.html.erb +13 -0
  46. data/app/views/bs5/examples/dropdowns/dark/_example.html.erb +2 -0
  47. data/app/views/bs5/examples/dropdowns/dark/snippet.html.erb +7 -0
  48. data/app/views/bs5/examples/dropdowns/directions/_example.html.erb +7 -0
  49. data/app/views/bs5/examples/dropdowns/directions/snippet1.html.erb +17 -0
  50. data/app/views/bs5/examples/dropdowns/directions/snippet2.html.erb +17 -0
  51. data/app/views/bs5/examples/dropdowns/directions/snippet3.html.erb +17 -0
  52. data/app/views/bs5/examples/dropdowns/menu_alignment/_example.html.erb +5 -0
  53. data/app/views/bs5/examples/dropdowns/menu_alignment/snippet1.html.erb +5 -0
  54. data/app/views/bs5/examples/dropdowns/menu_alignment/snippet2.html.erb +7 -0
  55. data/app/views/bs5/examples/dropdowns/menu_alignment/snippet3.html.erb +7 -0
  56. data/app/views/bs5/examples/dropdowns/menu_content/_example.html.erb +9 -0
  57. data/app/views/bs5/examples/dropdowns/menu_content/snippet1.html.erb +5 -0
  58. data/app/views/bs5/examples/dropdowns/menu_content/snippet2.html.erb +7 -0
  59. data/app/views/bs5/examples/dropdowns/menu_content/snippet3.html.erb +10 -0
  60. data/app/views/bs5/examples/dropdowns/menu_content/snippet4.html.erb +24 -0
  61. data/app/views/bs5/examples/dropdowns/menu_items/_example.html.erb +7 -0
  62. data/app/views/bs5/examples/dropdowns/menu_items/snippet1.html.erb +5 -0
  63. data/app/views/bs5/examples/dropdowns/menu_items/snippet2.html.erb +6 -0
  64. data/app/views/bs5/examples/dropdowns/menu_items/snippet3.html.erb +5 -0
  65. data/app/views/bs5/examples/dropdowns/menu_items/snippet4.html.erb +5 -0
  66. data/app/views/bs5/examples/dropdowns/single/_example.html.erb +4 -0
  67. data/app/views/bs5/examples/dropdowns/single/snippet1.html.erb +5 -0
  68. data/app/views/bs5/examples/dropdowns/single/snippet2.html.erb +47 -0
  69. data/app/views/bs5/examples/dropdowns/single/snippet3.html.erb +47 -0
  70. data/app/views/bs5/examples/dropdowns/sizing/_example.html.erb +3 -0
  71. data/app/views/bs5/examples/dropdowns/sizing/snippet1.html.erb +17 -0
  72. data/app/views/bs5/examples/dropdowns/sizing/snippet2.html.erb +17 -0
  73. data/app/views/bs5/examples/dropdowns/split/_example.html.erb +3 -0
  74. data/app/views/bs5/examples/dropdowns/split/snippet1.html.erb +35 -0
  75. data/app/views/bs5/examples/dropdowns/split/snippet2.html.erb +47 -0
  76. data/app/views/bs5/examples/list_group/active/snippet.html.erb +5 -5
  77. data/app/views/bs5/examples/list_group/default/snippet.html.erb +5 -5
  78. data/app/views/bs5/examples/list_group/disabled/snippet.html.erb +5 -5
  79. data/app/views/bs5/examples/list_group/flush/snippet.html.erb +5 -5
  80. data/app/views/bs5/examples/list_group/horizontal/snippet.html.erb +18 -18
  81. data/app/views/bs5/examples/list_group/style/default.html.erb +8 -8
  82. data/app/views/bs5/examples/modal/_examples.html.erb +9 -0
  83. data/app/views/bs5/examples/modal/_fullscreen.html.erb +2 -0
  84. data/app/views/bs5/examples/modal/_optional_sizes.html.erb +2 -0
  85. data/app/views/bs5/examples/modal/examples/snippet1.html.erb +12 -0
  86. data/app/views/bs5/examples/modal/examples/snippet2.html.erb +12 -0
  87. data/app/views/bs5/examples/modal/examples/snippet3.html.erb +14 -0
  88. data/app/views/bs5/examples/modal/examples/snippet4.html.erb +12 -0
  89. data/app/views/bs5/examples/modal/fullscreen/snippet1.html.erb +55 -0
  90. data/app/views/bs5/examples/modal/optional_sizes/snippet1.html.erb +23 -0
  91. data/app/views/bs5/examples/toasts/color_schemes/_example.html.erb +2 -0
  92. data/app/views/bs5/examples/toasts/color_schemes/snippet.html.erb +33 -0
  93. data/app/views/bs5/examples/toasts/custom_content/_example.html.erb +3 -0
  94. data/app/views/bs5/examples/toasts/custom_content/snippet1.html.erb +3 -0
  95. data/app/views/bs5/examples/toasts/custom_content/snippet2.html.erb +9 -0
  96. data/app/views/bs5/examples/toasts/default/_example.html.erb +2 -0
  97. data/app/views/bs5/examples/toasts/default/snippet.html.erb +17 -0
  98. data/app/views/bs5/examples/toasts/js_options/_example.html.erb +2 -0
  99. data/app/views/bs5/examples/toasts/js_options/snippet.html.erb +23 -0
  100. data/app/views/bs5/examples/toasts/placement/_example.html.erb +3 -0
  101. data/app/views/bs5/examples/toasts/placement/snippet1.html.erb +44 -0
  102. data/app/views/bs5/examples/toasts/placement/snippet2.html.erb +24 -0
  103. data/app/views/bs5/examples/toasts/stacking/_example.html.erb +2 -0
  104. data/app/views/bs5/examples/toasts/stacking/snippet.html.erb +37 -0
  105. data/app/views/bs5/pages/carousel.html.erb +3 -0
  106. data/app/views/bs5/pages/dropdowns.html.erb +10 -0
  107. data/app/views/bs5/pages/modal.html.erb +4 -0
  108. data/app/views/bs5/pages/toasts.html.erb +7 -0
  109. data/app/views/layouts/bs5/pages.html.erb +4 -0
  110. data/lib/bs5/version.rb +1 -1
  111. data/lib/generators/bs5/install/templates/bs5.js +24 -13
  112. data/lib/tasks/rubocop.rake +2 -0
  113. metadata +96 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fde2cc4be44c7ef1b396e7761904765ee31f1801acb7a88b1f4507cf302994c2
4
- data.tar.gz: d69a795e889ece4139479843c7e2e5f6ffa48df347f0463529513060c1401214
3
+ metadata.gz: 215200d1f6820e7e163cbd6b214f9952645e225f535c45a34254ef53f445480a
4
+ data.tar.gz: cc2f9daf0107719ee613670620ddb42e42a08b995bb32a7348003f9b74761584
5
5
  SHA512:
6
- metadata.gz: 4474fe20875677d9e8f45ca929d8e3ee95d6919a65065d472d2e12d9b74b765c6f491398b7691b8bf85b8a35d2b4cdc807839648f007a4836c47768ab865ac74
7
- data.tar.gz: d70751a40bda629d0ba59324cab991b94dca962248ec22625edd36344750476830ed655a4fd21c3b3e3a7b133609217c33267c96df3af39125a3600f3be5539b
6
+ metadata.gz: 8361ad156b1c5aa5a2e8d2e85482a891792fe28693aff0ab0de36a7acc258d1818934512a7b9c76d4be17de2e96f0c6207affa1f5765add53cff6bc83e999b74
7
+ data.tar.gz: 978c438199e216476b87a1eef92dcab0b9b8d828daca289c4eb7cc8b97fa802f75ef4620c06541870caeca578845303eb874fde87988036bea462918e5d0fc97
@@ -1,6 +1,6 @@
1
1
  <div class="<%= component_class %>" role="alert">
2
2
  <%= content %>
3
3
  <%- if is_dismissable %>
4
- <%= render Bs5::CloseButtonComponent.new(data: { 'bs-dismiss': :alert }) %>
4
+ <%= bs5_close_button(dismiss: :alert) %>
5
5
  <%- end %>
6
6
  </div>
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Bs5
4
4
  class AlertComponent < ViewComponent::Base
5
+ include ComponentsHelper
5
6
  STYLES = %i[primary secondary success danger warning info light dark].freeze
6
7
 
7
8
  attr_reader :color, :is_dismissable
@@ -47,6 +47,7 @@ module Bs5
47
47
  extract_color
48
48
  extract_outline
49
49
  extract_size
50
+ extract_dismiss
50
51
  end
51
52
 
52
53
  def extract_color
@@ -61,6 +62,10 @@ module Bs5
61
62
  @size = @options.delete(:size)
62
63
  end
63
64
 
65
+ def extract_dismiss
66
+ @dismiss = @options.delete(:dismiss)
67
+ end
68
+
64
69
  def merge_default_options
65
70
  @options.deep_merge!(default_options) do |_key, this_val, other_val|
66
71
  [this_val, other_val].join(' ').strip
@@ -68,7 +73,11 @@ module Bs5
68
73
  end
69
74
 
70
75
  def default_options
71
- { class: button_class }
76
+ default_options = { class: button_class }
77
+
78
+ default_options[:data] = { 'bs-dismiss': @dismiss } if @dismiss
79
+
80
+ default_options
72
81
  end
73
82
 
74
83
  def button_class
@@ -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
@@ -2,12 +2,19 @@
2
2
 
3
3
  module Bs5
4
4
  class CloseButtonComponent < ViewComponent::Base
5
- attr_reader :data
5
+ def initialize(options = {})
6
+ @options = options.symbolize_keys
6
7
 
7
- def initialize(disabled: false, white: false, data: nil)
8
- @disabled = disabled
9
- @white = white
10
- @data = data
8
+ @disabled = options.delete(:disabled)
9
+ @white = options.delete(:white)
10
+ @dismiss = options.delete(:dismiss)
11
+ @data = options.fetch(:data, {})
12
+ end
13
+
14
+ def data
15
+ @data['bs-dismiss'] = @dismiss if @dismiss
16
+
17
+ @data
11
18
  end
12
19
 
13
20
  private
@@ -21,7 +28,8 @@ module Bs5
21
28
  end
22
29
 
23
30
  def component_class
24
- class_names = ['btn-close']
31
+ class_names = Array(@options[:class])
32
+ class_names << 'btn-close'
25
33
  class_names << %w[btn-close-white] if white?
26
34
  class_names.join(' ')
27
35
  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