matestack-ui-bootstrap 1.4.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 (82) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +8 -0
  3. data/README.md +26 -0
  4. data/Rakefile +43 -0
  5. data/app/assets/images/avatar-placeholder.png +0 -0
  6. data/app/assets/images/icons/bootstrap-icons.svg +1 -0
  7. data/app/concepts/matestack/ui/bootstrap/apps/admin_template.rb +85 -0
  8. data/app/concepts/matestack/ui/bootstrap/components/accordion.rb +53 -0
  9. data/app/concepts/matestack/ui/bootstrap/components/alert.js +53 -0
  10. data/app/concepts/matestack/ui/bootstrap/components/alert.rb +34 -0
  11. data/app/concepts/matestack/ui/bootstrap/components/avatar.rb +27 -0
  12. data/app/concepts/matestack/ui/bootstrap/components/badge.rb +30 -0
  13. data/app/concepts/matestack/ui/bootstrap/components/breadcrumb.rb +46 -0
  14. data/app/concepts/matestack/ui/bootstrap/components/button.rb +54 -0
  15. data/app/concepts/matestack/ui/bootstrap/components/button_group.rb +36 -0
  16. data/app/concepts/matestack/ui/bootstrap/components/card.rb +100 -0
  17. data/app/concepts/matestack/ui/bootstrap/components/carousel.js +79 -0
  18. data/app/concepts/matestack/ui/bootstrap/components/carousel.rb +85 -0
  19. data/app/concepts/matestack/ui/bootstrap/components/chart.js +232 -0
  20. data/app/concepts/matestack/ui/bootstrap/components/chart.rb +71 -0
  21. data/app/concepts/matestack/ui/bootstrap/components/close.rb +30 -0
  22. data/app/concepts/matestack/ui/bootstrap/components/collapse.js +84 -0
  23. data/app/concepts/matestack/ui/bootstrap/components/collapse.rb +43 -0
  24. data/app/concepts/matestack/ui/bootstrap/components/dropdown.js +14 -0
  25. data/app/concepts/matestack/ui/bootstrap/components/dropdown.rb +116 -0
  26. data/app/concepts/matestack/ui/bootstrap/components/icon.rb +19 -0
  27. data/app/concepts/matestack/ui/bootstrap/components/list_group.rb +83 -0
  28. data/app/concepts/matestack/ui/bootstrap/components/modal.js +90 -0
  29. data/app/concepts/matestack/ui/bootstrap/components/modal.rb +106 -0
  30. data/app/concepts/matestack/ui/bootstrap/components/navbar.rb +120 -0
  31. data/app/concepts/matestack/ui/bootstrap/components/page_heading.rb +28 -0
  32. data/app/concepts/matestack/ui/bootstrap/components/pagination.rb +40 -0
  33. data/app/concepts/matestack/ui/bootstrap/components/popover.js +26 -0
  34. data/app/concepts/matestack/ui/bootstrap/components/popover.rb +92 -0
  35. data/app/concepts/matestack/ui/bootstrap/components/progress.rb +65 -0
  36. data/app/concepts/matestack/ui/bootstrap/components/scrollspy.rb +33 -0
  37. data/app/concepts/matestack/ui/bootstrap/components/section_card.rb +31 -0
  38. data/app/concepts/matestack/ui/bootstrap/components/spinner.rb +31 -0
  39. data/app/concepts/matestack/ui/bootstrap/components/tab_nav.rb +81 -0
  40. data/app/concepts/matestack/ui/bootstrap/components/tab_nav_content.rb +32 -0
  41. data/app/concepts/matestack/ui/bootstrap/components/toast.js +79 -0
  42. data/app/concepts/matestack/ui/bootstrap/components/toast.rb +99 -0
  43. data/app/concepts/matestack/ui/bootstrap/components/tooltip.js +26 -0
  44. data/app/concepts/matestack/ui/bootstrap/components/tooltip.rb +82 -0
  45. data/app/concepts/matestack/ui/bootstrap/content/collection/collection.rb +112 -0
  46. data/app/concepts/matestack/ui/bootstrap/content/collection/collection.scss +10 -0
  47. data/app/concepts/matestack/ui/bootstrap/content/collection/content.rb +101 -0
  48. data/app/concepts/matestack/ui/bootstrap/content/collection/filter.rb +33 -0
  49. data/app/concepts/matestack/ui/bootstrap/content/collection/paginate.rb +92 -0
  50. data/app/concepts/matestack/ui/bootstrap/content/figure.rb +7 -0
  51. data/app/concepts/matestack/ui/bootstrap/form/checkbox.rb +90 -0
  52. data/app/concepts/matestack/ui/bootstrap/form/date.js +38 -0
  53. data/app/concepts/matestack/ui/bootstrap/form/date.rb +98 -0
  54. data/app/concepts/matestack/ui/bootstrap/form/input.rb +123 -0
  55. data/app/concepts/matestack/ui/bootstrap/form/radio.rb +65 -0
  56. data/app/concepts/matestack/ui/bootstrap/form/select.haml +11 -0
  57. data/app/concepts/matestack/ui/bootstrap/form/select.rb +74 -0
  58. data/app/concepts/matestack/ui/bootstrap/form/submit.rb +20 -0
  59. data/app/concepts/matestack/ui/bootstrap/form/switch.rb +90 -0
  60. data/app/concepts/matestack/ui/bootstrap/layout/column.rb +47 -0
  61. data/app/concepts/matestack/ui/bootstrap/layout/container.rb +25 -0
  62. data/app/concepts/matestack/ui/bootstrap/layout/row.rb +15 -0
  63. data/app/concepts/matestack/ui/bootstrap/layout/sidebar.js +64 -0
  64. data/app/concepts/matestack/ui/bootstrap/layout/sidebar.rb +45 -0
  65. data/app/concepts/matestack/ui/bootstrap/layout/sidebar.scss +57 -0
  66. data/app/concepts/matestack/ui/bootstrap/pages/devise/sign_in.rb +40 -0
  67. data/app/concepts/matestack/ui/bootstrap/registry.rb +63 -0
  68. data/app/helpers/matestack/ui/bootstrap/application_helper.rb +13 -0
  69. data/app/javascript/matestack-ui-bootstrap/index.js +26 -0
  70. data/app/javascript/matestack-ui-bootstrap/stylesheets/matestack-ui-bootstrap.scss +65 -0
  71. data/app/javascript/packs/matestack-ui-bootstrap.js +2 -0
  72. data/config/routes.rb +2 -0
  73. data/config/webpack/development.js +5 -0
  74. data/config/webpack/environment.js +29 -0
  75. data/config/webpack/production.js +33 -0
  76. data/config/webpack/test.js +5 -0
  77. data/config/webpacker.yml +96 -0
  78. data/lib/matestack/ui/bootstrap.rb +27 -0
  79. data/lib/matestack/ui/bootstrap/engine.rb +26 -0
  80. data/lib/matestack/ui/bootstrap/version.rb +7 -0
  81. data/lib/tasks/matestack/ui/bootstrap_tasks.rake +66 -0
  82. metadata +137 -0
@@ -0,0 +1,90 @@
1
+ import * as bootstrap from 'bootstrap'
2
+
3
+ MatestackUiCore.Vue.component('matestack-ui-bootstrap-modal', {
4
+ mixins: [MatestackUiCore.componentMixin],
5
+
6
+ data() {
7
+ return {
8
+ modalInstance: undefined,
9
+ };
10
+ },
11
+
12
+ methods: {
13
+ toggle: function (){
14
+ this.modalInstance.toggle()
15
+ },
16
+ show: function (){
17
+ this.modalInstance.show()
18
+ },
19
+ hide: function (){
20
+ this.modalInstance.hide()
21
+ },
22
+ handleUpdate: function (){
23
+ this.modalInstance.handleUpdate()
24
+ },
25
+ dispose: function (){
26
+ this.modalInstance.dispose()
27
+ }
28
+ },
29
+
30
+ mounted: function() {
31
+ const self = this
32
+ var modal = self.$el
33
+ self.modalInstance = new bootstrap.Modal(modal)
34
+ },
35
+
36
+ created: function() {
37
+ const self = this
38
+ var eventHub = MatestackUiCore.matestackEventHub;
39
+ // toggle_on event registration
40
+ if(self.componentConfig["toggle_on"] != undefined){
41
+ var toggle_events = self.componentConfig["toggle_on"].split(",")
42
+ toggle_events.forEach(toggle_event => eventHub.$on(toggle_event.trim(), self.toggle));
43
+ }
44
+ // show_on event registration
45
+ if(self.componentConfig["show_on"] != undefined){
46
+ var show_events = self.componentConfig["show_on"].split(",")
47
+ show_events.forEach(show_event => eventHub.$on(show_event.trim(), self.show));
48
+ }
49
+ // hide_on event registration
50
+ if(self.componentConfig["hide_on"] != undefined){
51
+ var hide_events = self.componentConfig["hide_on"].split(",")
52
+ hide_events.forEach(hide_event => eventHub.$on(hide_event.trim(), self.hide));
53
+ }
54
+ // handle_update_on event registration
55
+ if(self.componentConfig["handle_update_on"] != undefined){
56
+ var handle_update_events = self.componentConfig["handle_update_on"].split(",")
57
+ handle_update_events.forEach(handle_update_event => eventHub.$on(handle_update_event.trim(), self.handle_update));
58
+ }
59
+ // dispose_on event registration
60
+ if(self.componentConfig["dispose_on"] != undefined){
61
+ var dispose_events = self.componentConfig["dispose_on"].split(",")
62
+ dispose_events.forEach(dispose_event => eventHub.$on(dispose_event.trim(), self.dispose));
63
+ }
64
+ },
65
+
66
+ beforeDestroy: function() {
67
+ const self = this
68
+ var eventHub = MatestackUiCore.matestackEventHub;
69
+ if(self.componentConfig["toggle_on"] != undefined){
70
+ var toggle_events = self.componentConfig["toggle_on"].split(",")
71
+ toggle_events.forEach(toggle_event => eventHub.$off(toggle_event.trim(), self.toggle));
72
+ }
73
+ if(self.componentConfig["show_on"] != undefined){
74
+ var show_events = self.componentConfig["show_on"].split(",")
75
+ show_events.forEach(show_event => eventHub.$off(show_event.trim(), self.show));
76
+ }
77
+ if(self.componentConfig["hide_on"] != undefined){
78
+ var hide_events = self.componentConfig["hide_on"].split(",")
79
+ hide_events.forEach(hide_event => eventHub.$off(hide_event.trim(), self.hide));
80
+ }
81
+ if(self.componentConfig["handle_update_on"] != undefined){
82
+ var handle_update_events = self.componentConfig["handle_update_on"].split(",")
83
+ handle_update_events.forEach(handle_update_event => eventHub.$off(handle_update_event.trim(), self.handle_update));
84
+ }
85
+ if(self.componentConfig["dispose_on"] != undefined){
86
+ var dispose_events = self.componentConfig["dispose_on"].split(",")
87
+ dispose_events.forEach(dispose_event => eventHub.$off(dispose_event.trim(), self.dispose));
88
+ }
89
+ },
90
+ });
@@ -0,0 +1,106 @@
1
+ class Matestack::Ui::Bootstrap::Components::Modal < Matestack::Ui::VueJsComponent
2
+ vue_js_component_name 'matestack-ui-bootstrap-modal'
3
+
4
+ # header attributes, expects a hash or string
5
+ # possible keys `:class, :text, :size`
6
+ optional :header
7
+ # body attributes, expects a hash or string
8
+ # possible keys `:class, :text`
9
+ optional :body
10
+ # footer is a dismiss button, expects a hash or string for button text
11
+ # possible keys `:class, :text`
12
+ optional :footer
13
+ optional :fade, :size
14
+ optional :fullscreen # fullscreen attribute, expects boolean or bootstrap breakpoint
15
+ optional :static, :keyboard, :scrollable, :centered
16
+ optional :modal_dialog_class, class: { as: :bs_class }, id: { as: :bs_id }
17
+ # event trigger
18
+ optional :toggle_on, :show_on, :hide_on, :handle_update_on, :dispose_on
19
+
20
+ optional :slots
21
+
22
+ def response
23
+ div modal_attributes do
24
+ div class: dialog_classes do
25
+ div class: "modal-content" do
26
+ header_partial if header || (slots && slots[:header])
27
+ body_partial if body || slots && slots[:body]
28
+ footer_partial if footer || (slots && slots[:footer])
29
+ yield_components
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ def header_partial
38
+ header = self.header.is_a?(Hash) ? self.header : { text: self.header }
39
+ div class: "modal-header" do
40
+ if slots && slots[:header]
41
+ slot slots[:header]
42
+ else
43
+ heading size: (header[:size] || 5), class: "modal-title #{header[:class]}", text: header[:text] if header[:text].present?
44
+ bs_close dismiss: 'modal'
45
+ end
46
+ end
47
+ end
48
+
49
+ def body_partial
50
+ body = self.body.is_a?(Hash) ? self.body : { text: self.body }
51
+ div class: "modal-body #{body[:class]}".strip do
52
+ if slots && slots[:body]
53
+ slot slots[:body]
54
+ else
55
+ plain body[:text] if body[:text].present?
56
+ end
57
+ end
58
+ end
59
+
60
+ def footer_partial
61
+ footer = self.footer.is_a?(Hash) ? self.footer : { text: self.footer }
62
+ div class: "modal-footer" do
63
+ slot slots[:footer] if slots && slots[:footer]
64
+ if footer[:text].present?
65
+ button class: "btn #{footer[:class].present? ? footer[:class] : 'btn-secondary'}",
66
+ data: { dismiss: 'modal' }, attributes: { type: 'button' },
67
+ text: footer[:text]
68
+ end
69
+ end
70
+ end
71
+
72
+ def modal_attributes
73
+ attributes = {}.tap do |hash|
74
+ hash[:class] = modal_classes
75
+ hash[:attributes] = { 'tabindex': "-1", 'aria-labelledby': "#{bs_id}Label", 'aria-hidden': "true" }
76
+ hash[:data] = {}.tap do |data|
77
+ data[:backdrop] = "static" if static
78
+ data[:keyboard] = "false" if keyboard
79
+ end
80
+ end
81
+ html_attributes.merge(
82
+ attributes
83
+ )
84
+ end
85
+
86
+ def modal_classes
87
+ [].tap do |classes|
88
+ classes << 'modal'
89
+ classes << 'fade' if fade || !fade.present?
90
+ classes << bs_class
91
+ end.join(' ').strip
92
+ end
93
+
94
+ def dialog_classes
95
+ [].tap do |classes|
96
+ classes << 'modal-dialog'
97
+ classes << 'modal-dialog-centered' if centered
98
+ classes << 'modal-dialog-scrollable' if scrollable
99
+ classes << "modal-#{size}" if size
100
+ if fullscreen.present?
101
+ classes << (fullscreen == true ? 'modal-fullscreen' : "modal-fullscreen-#{fullscreen}-down")
102
+ end
103
+ classes << modal_dialog_class
104
+ end.join(' ').strip
105
+ end
106
+ end
@@ -0,0 +1,120 @@
1
+ class Matestack::Ui::Bootstrap::Components::Navbar < Matestack::Ui::Component
2
+
3
+ POS_ATTRIBUTES = %i[fixed_top fixed_bottom sticky_top]
4
+ optional *POS_ATTRIBUTES
5
+
6
+ optional class: { as: :bs_class }
7
+ optional :items, :items_class, :theme, :hide_at, :color, :container_size
8
+ optional :collapse_class
9
+ # brand expect hash or string, possible keys for hash: text, path, img
10
+ optional :brand
11
+ # toogle expect hash or a symbol (:left or :right),
12
+ # possible keys for hash: position, class
13
+ optional :toggle
14
+
15
+ def prepare
16
+ @toggle = self.toggle.is_a?(Hash) ? self.toggle : { position: self.toggle }
17
+ end
18
+
19
+ def response
20
+ nav navbar_attributes do
21
+ bs_container size: "#{container_size.present? ? container_size : "fluid" }" do
22
+ # custom elements for navbar
23
+ if options[:slots] && options[:slots][:custom_items]
24
+ slot options[:slots][:custom_items]
25
+ else
26
+ toggle_button if @toggle[:position] == :left
27
+ brand_partial
28
+ toggle_button if !@toggle[:position].present? || @toggle[:position] == :right
29
+ navbar_content_partial
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ def brand_partial
38
+ brand = self.brand.is_a?(Hash) ? self.brand : { text: self.brand }
39
+ path = brand[:path].present? ? brand[:path] : "/"
40
+ link class: "navbar-brand", path: path do
41
+ img height: 40, path: brand[:img], attributes: { loading: "lazy" } if brand[:img].present?
42
+ plain brand[:text]
43
+ end
44
+ end
45
+
46
+ def navbar_content_partial
47
+ div class: "collapse navbar-collapse #{collapse_class}", id: 'matestackNavbarContent' do
48
+ ul class: items_classes do
49
+ items.each do |item|
50
+ li class: "nav-item" do
51
+ if item[:type] == :link
52
+ link class: "nav-link", path: item[:path] do
53
+ bs_icon name: item[:icon], size: 20 if item[:icon]
54
+ span class: "ps-3", text: item[:text] if item[:text]
55
+ end
56
+ else
57
+ transition class: "nav-link", path: item[:path], delay: item[:delay] do
58
+ bs_icon name: item[:icon], size: 20 if item[:icon]
59
+ span class: "ps-3", text: item[:text] if item[:text]
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ yield_components
66
+ end
67
+ end
68
+
69
+ def navbar_attributes
70
+ html_attributes.merge(
71
+ class: navbar_classes
72
+ )
73
+ end
74
+
75
+ def navbar_classes
76
+ [].tap do |classes|
77
+ classes << "navbar"
78
+ POS_ATTRIBUTES.each do |pos|
79
+ classes << "#{pos}".gsub('_','-') if self.send("#{pos}")
80
+ end
81
+ classes << "navbar-expand-#{ (hide_at.present? ? hide_at : "lg") }"
82
+ classes << "navbar-#{theme}" if theme.present?
83
+ classes << (color.present? ? "bg-#{color}" : "bg-#{theme}") if theme || color
84
+ classes << bs_class
85
+ end.join(' ').strip
86
+ end
87
+
88
+ def toggle_button
89
+ button toggle_attributes do
90
+ bs_icon name: "list", size: 25, class: "text-muted"
91
+ end
92
+ # button toggle_attributes do
93
+ # span class: "navbar-toggler-icon"
94
+ # end
95
+ end
96
+
97
+ def toggle_attributes
98
+ toggle_classes = [].tap do |classes|
99
+ classes << 'd-lg-none'
100
+ classes << 'btn btn-link'
101
+ classes << "ms-auto" if @toggle[:position] == :right
102
+ classes << "me-auto" if @toggle[:position] == :left
103
+ classes << @toggle[:class] if @toggle[:class]
104
+ end.join(' ').strip
105
+
106
+ {}.tap do |hash|
107
+ hash[:class] = toggle_classes
108
+ hash[:data] = { toggle: 'collapse', target: '#matestackNavbarContent' }
109
+ hash[:attributes] = { 'aria-controls': 'matestackNavbarContent', 'aria-expanded': 'false', 'aria-label': 'Toggle navigation' }
110
+ end
111
+ end
112
+
113
+ def items_classes
114
+ [].tap do |classes|
115
+ classes << 'navbar-nav'
116
+ classes << (items_class.present? ? items_class : "ms-auto mb-2 mb-lg-0")
117
+ # classes << "ms-auto" unless items_class.present?
118
+ end.join(' ').strip
119
+ end
120
+ end
@@ -0,0 +1,28 @@
1
+ class Matestack::Ui::Bootstrap::Components::PageHeading < Matestack::Ui::Component
2
+
3
+ optional :title, :subtitle, :icon
4
+
5
+ def response
6
+ section class: "mb-5" do
7
+ bs_row do
8
+ bs_col do
9
+ div class: "page-heading-heading" do
10
+ heading size: 2 do
11
+ bs_icon name: icon, size: "35" if icon.present?
12
+ plain title
13
+ end
14
+ heading size: 6, class: "text-muted" do
15
+ plain subtitle
16
+ end
17
+ end
18
+ end
19
+ bs_col md: 12, xl: 4, class: "text-xl-end mt-3" do
20
+ div class: "page-heading-actions text-xl-end" do
21
+ yield_components
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,40 @@
1
+ class Matestack::Ui::Bootstrap::Components::Pagination < Matestack::Ui::Component
2
+
3
+ optional :aria_label, :size, class: { as: :bs_class }
4
+ optional :items
5
+
6
+ def response
7
+ nav pagination_attributes do
8
+ ul class: ul_classes do
9
+ if items.present?
10
+ items.each do |item|
11
+ li class: "page-item #{ 'active' if item[:active] }" do
12
+ if item[:type] == :link
13
+ link path: item[:path], text: item[:text], class: 'page-link'
14
+ else
15
+ transition path: item[:path], text: item[:text], class: 'page-link'
16
+ end
17
+ end
18
+ end
19
+ end
20
+ yield_components
21
+ end
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ def pagination_attributes
28
+ html_attributes.merge(
29
+ attributes: { 'aria-label': "#{aria_label || 'Page navigation'}" }
30
+ )
31
+ end
32
+
33
+ def ul_classes
34
+ [].tap do |classes|
35
+ classes << "pagination"
36
+ classes << "pagination-#{size}" if size.present?
37
+ end.join(' ').strip
38
+ end
39
+
40
+ end
@@ -0,0 +1,26 @@
1
+ import * as bootstrap from 'bootstrap'
2
+
3
+ MatestackUiCore.Vue.component('matestack-ui-bootstrap-popover', {
4
+
5
+ mixins: [MatestackUiCore.componentMixin],
6
+ data() {
7
+ return {
8
+ popoverInstance: undefined
9
+
10
+ };
11
+ },
12
+ methods: {
13
+
14
+ },
15
+ mounted: function() {
16
+ const self = this;
17
+ var popover = self.$el
18
+ self.popoverInstance = new bootstrap.Popover(popover, {})
19
+ },
20
+ created: function() {
21
+ },
22
+
23
+ beforeDestroy: function() {
24
+
25
+ },
26
+ });
@@ -0,0 +1,92 @@
1
+ class Matestack::Ui::Bootstrap::Components::Popover < Matestack::Ui::VueJsComponent
2
+ vue_js_component_name "matestack-ui-bootstrap-popover"
3
+
4
+ # How i imagine using a popover
5
+ #
6
+ # popover content: 'A popover' do
7
+ # button text: 'Popover'
8
+ # end
9
+ # => <span data-toggle="popover" data-content="A Popover"><button>Popover</button></span>
10
+ #
11
+ # popover content: 'A popover', tag: :div do
12
+ # button text: 'Popover'
13
+ # end
14
+ # => <div data-toggle="popover" data-content="A Popover"><button>Popover</button></div>
15
+ #
16
+ # popover text: 'Popover', content: 'A popover', tag: :button # should be default
17
+ # => <button data-toggle="popover" data-content="A Popover">Popover</button>
18
+ #
19
+ # popover text: 'Popover', content: 'A popover', dismissible: true, tabindex: 2
20
+ # => <a tabindex="2" data-toggle="popover" data-content="A Popover" data-trigger="focus" role="button">Popover</a>
21
+ #
22
+ # popover text: 'Popover', content: 'A popover', tag: :a
23
+ # => <a data-toggle="popover" data-content="A Popover">Popover</a>
24
+
25
+
26
+ DATA_ALIAS_ATTRIBUTES = %i[container delay selector html template fallback_placement]
27
+
28
+ DATA_ALIAS_ATTRIBUTES.each do |attribute|
29
+ optional "#{attribute}": { as: :"bs_#{attribute}"}
30
+ end
31
+
32
+ # TODO:
33
+ # for security reasons the sanitize, sanitizeFn and whiteList options cannot be supplied using data attributes.
34
+ # sanitize sanitize_fn white_list
35
+
36
+ optional class: { as: :bs_class }
37
+ optional id: { as: :bs_id }
38
+ DATA_ATTRIBUTES = %i[tag content title text variant animation placement tabindex trigger boundary offset popper_config]
39
+ optional *DATA_ATTRIBUTES
40
+ # :tag # different element to apply: div, span, links, button, ...
41
+ # :content, :title # popover title and content strings
42
+ # :animation # boolean, default true
43
+ # :variant, :text # button styling & text
44
+ # :placement # placement direction as string
45
+
46
+ def response
47
+ if tag.present?
48
+ public_send(tag, popover_attributes) do
49
+ content_partial
50
+ end
51
+ else
52
+ btn popover_attributes do
53
+ content_partial
54
+ end
55
+ end
56
+ end
57
+
58
+ protected
59
+
60
+ def content_partial
61
+ plain text if text
62
+ yield_components unless text
63
+ end
64
+
65
+ def popover_attributes
66
+ attributes = {}.tap do |hash|
67
+ hash[:class] = popover_classes
68
+ hash[:attributes] = { role: (text ? 'button': nil), title: "#{title}", tabindex: "#{tabindex}" }
69
+ hash[:data] = {}.tap do |data|
70
+ DATA_ALIAS_ATTRIBUTES.each do |attribute|
71
+ data["bs-#{attribute}"] = self.send(:"bs_#{attribute}") if self.send(:"bs_#{attribute}")
72
+ end
73
+ (DATA_ATTRIBUTES - [:tag, :text, :variant]).each do |attribute|
74
+ data["bs-#{attribute}"] = self.send(:"#{attribute}") if self.send(:"#{attribute}")
75
+ end
76
+ data["bs-toggle"] = "popover"
77
+ end
78
+ end
79
+ html_attributes.merge(
80
+ attributes
81
+ )
82
+ end
83
+
84
+ def popover_classes
85
+ [].tap do |classes|
86
+ classes << "d-inline-block" unless text
87
+ classes << "btn btn-#{variant}" if variant
88
+ classes << bs_class
89
+ end.join(' ').strip
90
+ end
91
+
92
+ end