bs5 0.0.21 → 0.0.26

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/bs5_manifest.js +1 -0
  3. data/app/assets/javascripts/bs5/application.js +13 -0
  4. data/app/components/bs5/alert_component.html.erb +1 -1
  5. data/app/components/bs5/alert_component.rb +1 -0
  6. data/app/components/bs5/button_tag_component.rb +10 -1
  7. data/app/components/bs5/close_button_component.rb +14 -6
  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/example_component.html.erb +2 -1
  13. data/app/components/bs5/toast/body_component.html.erb +3 -0
  14. data/app/components/bs5/toast/body_component.rb +8 -0
  15. data/app/components/bs5/toast/header_component.html.erb +4 -0
  16. data/app/components/bs5/toast/header_component.rb +9 -0
  17. data/app/components/bs5/toast_component.html.erb +8 -0
  18. data/app/components/bs5/toast_component.rb +72 -0
  19. data/app/components/bs5/toast_container_component.html.erb +3 -0
  20. data/app/components/bs5/toast_container_component.rb +19 -0
  21. data/app/helpers/bs5/components_helper.rb +2 -2
  22. data/app/views/bs5/examples/alert/color/snippet.html.erb +8 -24
  23. data/app/views/bs5/examples/alert/default/snippet.html.erb +1 -3
  24. data/app/views/bs5/examples/dropdowns/dark/_example.html.erb +2 -0
  25. data/app/views/bs5/examples/dropdowns/dark/snippet.html.erb +7 -0
  26. data/app/views/bs5/examples/dropdowns/directions/_example.html.erb +7 -0
  27. data/app/views/bs5/examples/dropdowns/directions/snippet1.html.erb +17 -0
  28. data/app/views/bs5/examples/dropdowns/directions/snippet2.html.erb +17 -0
  29. data/app/views/bs5/examples/dropdowns/directions/snippet3.html.erb +17 -0
  30. data/app/views/bs5/examples/dropdowns/menu_alignment/_example.html.erb +5 -0
  31. data/app/views/bs5/examples/dropdowns/menu_alignment/snippet1.html.erb +5 -0
  32. data/app/views/bs5/examples/dropdowns/menu_alignment/snippet2.html.erb +7 -0
  33. data/app/views/bs5/examples/dropdowns/menu_alignment/snippet3.html.erb +7 -0
  34. data/app/views/bs5/examples/dropdowns/menu_content/_example.html.erb +9 -0
  35. data/app/views/bs5/examples/dropdowns/menu_content/snippet1.html.erb +5 -0
  36. data/app/views/bs5/examples/dropdowns/menu_content/snippet2.html.erb +7 -0
  37. data/app/views/bs5/examples/dropdowns/menu_content/snippet3.html.erb +10 -0
  38. data/app/views/bs5/examples/dropdowns/menu_content/snippet4.html.erb +24 -0
  39. data/app/views/bs5/examples/dropdowns/menu_items/_example.html.erb +7 -0
  40. data/app/views/bs5/examples/dropdowns/menu_items/snippet1.html.erb +5 -0
  41. data/app/views/bs5/examples/dropdowns/menu_items/snippet2.html.erb +6 -0
  42. data/app/views/bs5/examples/dropdowns/menu_items/snippet3.html.erb +5 -0
  43. data/app/views/bs5/examples/dropdowns/menu_items/snippet4.html.erb +5 -0
  44. data/app/views/bs5/examples/dropdowns/single/_example.html.erb +4 -0
  45. data/app/views/bs5/examples/dropdowns/single/snippet1.html.erb +5 -0
  46. data/app/views/bs5/examples/dropdowns/single/snippet2.html.erb +47 -0
  47. data/app/views/bs5/examples/dropdowns/single/snippet3.html.erb +47 -0
  48. data/app/views/bs5/examples/dropdowns/sizing/_example.html.erb +3 -0
  49. data/app/views/bs5/examples/dropdowns/sizing/snippet1.html.erb +17 -0
  50. data/app/views/bs5/examples/dropdowns/sizing/snippet2.html.erb +17 -0
  51. data/app/views/bs5/examples/dropdowns/split/_example.html.erb +3 -0
  52. data/app/views/bs5/examples/dropdowns/split/snippet1.html.erb +35 -0
  53. data/app/views/bs5/examples/dropdowns/split/snippet2.html.erb +47 -0
  54. data/app/views/bs5/examples/list_group/active/snippet.html.erb +5 -5
  55. data/app/views/bs5/examples/list_group/default/snippet.html.erb +5 -5
  56. data/app/views/bs5/examples/list_group/disabled/snippet.html.erb +5 -5
  57. data/app/views/bs5/examples/list_group/flush/snippet.html.erb +5 -5
  58. data/app/views/bs5/examples/list_group/horizontal/snippet.html.erb +18 -18
  59. data/app/views/bs5/examples/list_group/style/default.html.erb +8 -8
  60. data/app/views/bs5/examples/toasts/color_schemes/_example.html.erb +2 -0
  61. data/app/views/bs5/examples/toasts/color_schemes/snippet.html.erb +33 -0
  62. data/app/views/bs5/examples/toasts/custom_content/_example.html.erb +3 -0
  63. data/app/views/bs5/examples/toasts/custom_content/snippet1.html.erb +3 -0
  64. data/app/views/bs5/examples/toasts/custom_content/snippet2.html.erb +9 -0
  65. data/app/views/bs5/examples/toasts/default/_example.html.erb +2 -0
  66. data/app/views/bs5/examples/toasts/default/snippet.html.erb +17 -0
  67. data/app/views/bs5/examples/toasts/js_options/_example.html.erb +2 -0
  68. data/app/views/bs5/examples/toasts/js_options/snippet.html.erb +23 -0
  69. data/app/views/bs5/examples/toasts/placement/_example.html.erb +3 -0
  70. data/app/views/bs5/examples/toasts/placement/snippet1.html.erb +44 -0
  71. data/app/views/bs5/examples/toasts/placement/snippet2.html.erb +24 -0
  72. data/app/views/bs5/examples/toasts/stacking/_example.html.erb +2 -0
  73. data/app/views/bs5/examples/toasts/stacking/snippet.html.erb +37 -0
  74. data/app/views/bs5/pages/dropdowns.html.erb +10 -0
  75. data/app/views/bs5/pages/toasts.html.erb +7 -0
  76. data/app/views/layouts/bs5/pages.html.erb +3 -0
  77. data/lib/bs5/engine.rb +6 -0
  78. data/lib/bs5/version.rb +1 -1
  79. data/lib/generators/bs5/install/templates/bs5.js +24 -13
  80. data/lib/tasks/rubocop.rake +1 -1
  81. metadata +61 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec13b3d4b022a8136ed67a797111aa537f4ba05a6dbf30ad9b8e8eb9099577e3
4
- data.tar.gz: 0ac4e60fc0ad95d59e567133fd7dc5bb2d67555a0e97b7b2ceddc7e8b5ec0bd9
3
+ metadata.gz: 14b5dca44bb60e64b8eb925c7a617c2e7f6214bf33e1d35a44454b0fc617351c
4
+ data.tar.gz: 49dc980ff0a533774090543e1f8920c3ae7d4d4037c9fd84a5aa3425bb1a8faf
5
5
  SHA512:
6
- metadata.gz: 942a8e42a2cbaf93928690249f8ab1f9e914a3e9265529e3aff0d76d8bb739218dbeac3d420bbd1f0b154933d182d5a0433dc3fb031f82c496f683b4484856d8
7
- data.tar.gz: 3e0c2dfd11427835d34de52ec59fded37e0904ce509ad7abec7595676871526c76c79ede5bfcda74a6e47890d85293c85c89bb575191e1041c50bed4af5e2c93
6
+ metadata.gz: 2329cc993b4ab1d5930802dfab3f71d878843ce20ebc51bd861c488179e9c4f993b81e48bf1fca791af6886e735de751bbe4ae90f04e641249409e4bade1fc0f
7
+ data.tar.gz: ae01cfa6597d19ea0cacff53947b70f3ca8e4c0da463ede006ceef58268efa021a8851684bda0f1702db6a6092b15239fc9b633d62877bed44c2bf330e85b0dd
@@ -1 +1,2 @@
1
1
  //= link_directory ../stylesheets/bs5 .css
2
+ //= link_directory ../javascripts/bs5 .js
@@ -0,0 +1,13 @@
1
+ window.addEventListener("load", initBs5);
2
+ window.addEventListener("turbolinks:load", initBs5);
3
+
4
+ function initBs5(event) {
5
+ document.querySelectorAll('[data-bs5="copy"]').forEach(function (item) {
6
+ item.addEventListener("click", handleBs5CopyButtonClick);
7
+ });
8
+ }
9
+
10
+ function handleBs5CopyButtonClick(event) {
11
+ var code = event.target.closest("div").querySelector("code").innerText;
12
+ navigator.clipboard.writeText(code);
13
+ }
@@ -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
@@ -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
@@ -2,7 +2,8 @@
2
2
  <div class="border rounded-2 p-4">
3
3
  <%= render template: snippet %>
4
4
  </div>
5
- <div class="highlight p-4">
5
+ <div class="highlight p-4 position-relative">
6
+ <%= bs5_button_tag('Copy', color: :primary, outline: true, size: :small, class: 'position-absolute top-0 end-0 mt-2 me-2', data: { bs5: 'copy' }) %>
6
7
  <pre class='mb-0'><code><%= highlight %></code></pre>
7
8
  </div>
8
9
  </div>
@@ -0,0 +1,3 @@
1
+ <div class="toast-body">
2
+ <%= content %>
3
+ </div>
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bs5
4
+ module Toast
5
+ class BodyComponent < ViewComponent::Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ <div class="toast-header">
2
+ <%= content %>
3
+ <%= bs5_close_button(dismiss: :toast) %>
4
+ </div>
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bs5
4
+ module Toast
5
+ class HeaderComponent < ViewComponent::Base
6
+ include ComponentsHelper
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ <%= tag.div(component_attributes) do %>
2
+ <%= header %>
3
+ <%= body %>
4
+
5
+ <%- if close_button? && !header? %>
6
+ <%= bs5_close_button(white: white_text?, class: 'ms-auto me-2', dismiss: :toast) %>
7
+ <% end %>
8
+ <% end %>
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bs5
4
+ class ToastComponent < ViewComponent::Base
5
+ include ViewComponent::SlotableV2
6
+ include ComponentsHelper
7
+ using HashRefinement
8
+
9
+ attr_reader :color
10
+
11
+ renders_one :header, Bs5::Toast::HeaderComponent
12
+ renders_one :body, Bs5::Toast::BodyComponent
13
+
14
+ def initialize(options = {})
15
+ @options = options.symbolize_keys
16
+ @color = @options.delete(:color)
17
+ @close_button = @options.fetch(:close_button, true)
18
+ @data_options = @options.extract!(:animation, :autohide, :delay)
19
+ end
20
+
21
+ def header?
22
+ !!header
23
+ end
24
+
25
+ def component_attributes
26
+ default_options = {
27
+ role: :alert,
28
+ aria: { live: 'assertive', atomic: true },
29
+ data: data_options
30
+ }
31
+
32
+ @options[:class] = component_class
33
+
34
+ @options.merge(default_options)
35
+ end
36
+
37
+ def white_text?
38
+ color? && color.in?(%i[primary secondary success danger dark])
39
+ end
40
+
41
+ private
42
+
43
+ def data_options
44
+ @data_options.prefix_keys_with_bs
45
+ end
46
+
47
+ def component_class
48
+ class_names = Array(@options[:class])
49
+ class_names << 'toast'
50
+ class_names << contextual_class
51
+ class_names.compact.join(' ')
52
+ end
53
+
54
+ def contextual_class
55
+ return unless color?
56
+
57
+ class_names = ['border-0']
58
+ class_names << "bg-#{color}"
59
+ class_names << 'text-white' if white_text?
60
+
61
+ class_names
62
+ end
63
+
64
+ def color?
65
+ !!color
66
+ end
67
+
68
+ def close_button?
69
+ @close_button
70
+ end
71
+ end
72
+ end