bs5 0.0.21 → 0.0.26

Sign up to get free protection for your applications and to get access to all the features.
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