bootstrap5_helper 1.0.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +506 -0
- data/Rakefile +3 -0
- data/lib/bootstrap5_helper/accordion/item.rb +107 -0
- data/lib/bootstrap5_helper/accordion.rb +46 -0
- data/lib/bootstrap5_helper/alert.rb +69 -0
- data/lib/bootstrap5_helper/badge.rb +53 -0
- data/lib/bootstrap5_helper/callout.rb +58 -0
- data/lib/bootstrap5_helper/card.rb +204 -0
- data/lib/bootstrap5_helper/component.rb +156 -0
- data/lib/bootstrap5_helper/configuration.rb +65 -0
- data/lib/bootstrap5_helper/constants.rb +28 -0
- data/lib/bootstrap5_helper/dropdown/menu.rb +157 -0
- data/lib/bootstrap5_helper/dropdown.rb +121 -0
- data/lib/bootstrap5_helper/initialize.rb +19 -0
- data/lib/bootstrap5_helper/input_group.rb +70 -0
- data/lib/bootstrap5_helper/modal.rb +224 -0
- data/lib/bootstrap5_helper/nav.rb +145 -0
- data/lib/bootstrap5_helper/offcanvas/content.rb +127 -0
- data/lib/bootstrap5_helper/offcanvas.rb +169 -0
- data/lib/bootstrap5_helper/page_header.rb +42 -0
- data/lib/bootstrap5_helper/railtie.rb +12 -0
- data/lib/bootstrap5_helper/spinner.rb +44 -0
- data/lib/bootstrap5_helper/tab/content.rb +58 -0
- data/lib/bootstrap5_helper/tab.rb +72 -0
- data/lib/bootstrap5_helper/toast.rb +7 -0
- data/lib/bootstrap5_helper/version.rb +3 -0
- data/lib/bootstrap5_helper.rb +547 -0
- data/lib/tasks/bootstrap5_helper_tasks.rake +4 -0
- metadata +152 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
module Bootstrap5Helper
|
2
|
+
# Builds a Nav Component that can be used in other components.
|
3
|
+
#
|
4
|
+
#
|
5
|
+
class Nav < Component
|
6
|
+
# Class constructor
|
7
|
+
#
|
8
|
+
# @param [ActionView] template
|
9
|
+
# @param [Symbol|Hash] tag_or_options
|
10
|
+
# @param [Hash] opts
|
11
|
+
# @option opts [String] :id
|
12
|
+
# @option opts [String] :class
|
13
|
+
# @option opts [Hash] :data
|
14
|
+
# @option opts [Hash] :child
|
15
|
+
#
|
16
|
+
def initialize(template, tag_or_options = nil, opts = {}, &block)
|
17
|
+
super(template)
|
18
|
+
|
19
|
+
@tag, args = parse_tag_or_options(tag_or_options, opts)
|
20
|
+
@tag ||= config({ navs: :base }, :ul)
|
21
|
+
|
22
|
+
@id = args.fetch(:id, uuid)
|
23
|
+
@class = args.fetch(:class, '')
|
24
|
+
@data = args.fetch(:data, {})
|
25
|
+
@child = args.fetch(:child, {})
|
26
|
+
@content = block || proc { '' }
|
27
|
+
|
28
|
+
@dropdown = Dropdown.new(@template)
|
29
|
+
end
|
30
|
+
|
31
|
+
# rubocop:disable Metrics/MethodLength
|
32
|
+
|
33
|
+
# Adds an nav-item to the nav component. this method gets used when the nav-item
|
34
|
+
# links to content in a tab or something.
|
35
|
+
#
|
36
|
+
# @param [Symbol|String] target
|
37
|
+
# @param [Hash] opts
|
38
|
+
# @option opts [String] :id
|
39
|
+
# @option opts [String] :class
|
40
|
+
# @option opts [Hash] :data
|
41
|
+
# @option opts [Hash] :aria
|
42
|
+
# @return [String]
|
43
|
+
#
|
44
|
+
def item(target, opts = {})
|
45
|
+
id = opts.fetch(:id, nil)
|
46
|
+
klass = opts.fetch(:class, '')
|
47
|
+
data = opts.fetch(:data, {})
|
48
|
+
aria = opts.fetch(:aria, {})
|
49
|
+
|
50
|
+
nav_item_wrapper id: id, data: data do
|
51
|
+
content_tag(
|
52
|
+
:a,
|
53
|
+
class: "nav-link #{klass}",
|
54
|
+
href: "##{target}",
|
55
|
+
tabindex: -1,
|
56
|
+
data: @child[:data],
|
57
|
+
aria: aria
|
58
|
+
) do
|
59
|
+
block_given? ? yield : target.to_s.titleize
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
# rubocop:enable Metrics/MethodLength
|
64
|
+
|
65
|
+
# Use this when the nav item is nothing more than a hyperlink.
|
66
|
+
#
|
67
|
+
# @param [String|NilClass] name
|
68
|
+
# @param [Hash|NilClass] options
|
69
|
+
# @param [Hash|NilClass] html_options
|
70
|
+
# @return [String]
|
71
|
+
#
|
72
|
+
def link(name = nil, options = nil, html_options = nil, &block)
|
73
|
+
html_options ||= {}
|
74
|
+
html_options[:class] = (html_options[:class] || '') << ' nav-link'
|
75
|
+
|
76
|
+
nav_item_wrapper do
|
77
|
+
@template.link_to(name, options, html_options, &block)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Creates a dropdown menu for the nav.
|
82
|
+
#
|
83
|
+
# @param [NilClass|Symbol|String] name
|
84
|
+
# @param [Hash] opts
|
85
|
+
# @option opts [String] :id
|
86
|
+
# @option opts [String] :class
|
87
|
+
# @option opts [Hash] :data
|
88
|
+
# @option opts [Hash] :aria
|
89
|
+
# @return [String]
|
90
|
+
#
|
91
|
+
def dropdown(name, opts = {}, &block)
|
92
|
+
id = opts.fetch(:id, nil)
|
93
|
+
klass = opts.fetch(:class, '')
|
94
|
+
data = opts.fetch(:data, {})
|
95
|
+
aria = opts.fetch(:aria, {}).merge(haspopup: true, expanded: false)
|
96
|
+
|
97
|
+
nav_item_wrapper id: id, class: 'dropdown', data: data do
|
98
|
+
content_tag(
|
99
|
+
:a,
|
100
|
+
name,
|
101
|
+
class: "nav-link dropdown-toggle #{klass}",
|
102
|
+
href: '#',
|
103
|
+
data: { 'bs-toggle' => 'dropdown' },
|
104
|
+
role: 'button',
|
105
|
+
aria: aria
|
106
|
+
) + @dropdown.menu(opts, &block).to_s.html_safe
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# String representation of the object.
|
111
|
+
#
|
112
|
+
# @return [String]
|
113
|
+
#
|
114
|
+
def to_s
|
115
|
+
content_tag(@tag, id: @id, class: "nav #{@class}") do
|
116
|
+
@content.call(self)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
# Decorator for elements require a wrapper component.
|
123
|
+
#
|
124
|
+
# @return [String]
|
125
|
+
#
|
126
|
+
def nav_item_wrapper(opts = {}, &block)
|
127
|
+
id = opts.fetch(:id, '')
|
128
|
+
klass = opts.fetch(:class, '')
|
129
|
+
data = opts.fetch(:data, {})
|
130
|
+
|
131
|
+
case @tag
|
132
|
+
when :nav
|
133
|
+
block.call
|
134
|
+
when :ul
|
135
|
+
content_tag(
|
136
|
+
:li,
|
137
|
+
id: id,
|
138
|
+
class: "nav-item #{klass}",
|
139
|
+
data: data,
|
140
|
+
&block
|
141
|
+
)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Bootstrap5Helper
|
2
|
+
class Offcanvas
|
3
|
+
# Builds a Content component for use in offcanvas.
|
4
|
+
#
|
5
|
+
#
|
6
|
+
class Content < Component
|
7
|
+
# Constructor description...
|
8
|
+
#
|
9
|
+
#
|
10
|
+
# @param [Hash] opts
|
11
|
+
# @return [ClassName]
|
12
|
+
#
|
13
|
+
def initialize(template, opts = {}, &block)
|
14
|
+
super(template)
|
15
|
+
|
16
|
+
@id = opts.fetch(:id, uuid)
|
17
|
+
@class = opts.fetch(:class, '')
|
18
|
+
@data = opts.fetch(:data, {})
|
19
|
+
@aria = opts.fetch(:aria, {})
|
20
|
+
@scrollable = opts.fetch(:scrollable, false)
|
21
|
+
@position = opts.fetch(:position, 'start')
|
22
|
+
@content = block || proc { '' }
|
23
|
+
end
|
24
|
+
|
25
|
+
# @todo
|
26
|
+
#
|
27
|
+
#
|
28
|
+
def header(opts = {}, &block)
|
29
|
+
id = opts.fetch(:id, nil)
|
30
|
+
klass = opts.fetch(:class, '')
|
31
|
+
data = opts.fetch(:data, {})
|
32
|
+
|
33
|
+
content_tag(
|
34
|
+
config({ offcanvas: :header }, :div),
|
35
|
+
id: id,
|
36
|
+
class: "offcanvas-header #{klass}",
|
37
|
+
data: data,
|
38
|
+
&block
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @todo
|
43
|
+
#
|
44
|
+
#
|
45
|
+
def title(text_or_options = nil, opts = {}, &block)
|
46
|
+
text, args = parse_text_or_options(text_or_options, opts)
|
47
|
+
|
48
|
+
id = args.fetch(:id, nil)
|
49
|
+
klass = args.fetch(:class, '')
|
50
|
+
data = args.fetch(:data, {})
|
51
|
+
|
52
|
+
content_tag(
|
53
|
+
config({ offcanvas: :title }, :h6),
|
54
|
+
text,
|
55
|
+
id: id,
|
56
|
+
class: "offcanvas-title #{klass}",
|
57
|
+
data: data,
|
58
|
+
&block
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @todo
|
63
|
+
#
|
64
|
+
#
|
65
|
+
def close_button(opts = {})
|
66
|
+
klass = opts.fetch(:class, '')
|
67
|
+
|
68
|
+
content_tag(
|
69
|
+
config({ offcanvas: :close }, :button),
|
70
|
+
class: block_given? ? klass : 'btn-close',
|
71
|
+
data: { 'bs-dismiss': 'offcanvas' },
|
72
|
+
aria: { label: 'Close' }
|
73
|
+
) do
|
74
|
+
block_given? ? yield : xbutton
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @todo
|
79
|
+
#
|
80
|
+
#
|
81
|
+
def body(opts = {}, &block)
|
82
|
+
id = opts.fetch(:id, nil)
|
83
|
+
klass = opts.fetch(:class, '')
|
84
|
+
data = opts.fetch(:data, {})
|
85
|
+
aria = opts.fetch(:aria, {})
|
86
|
+
|
87
|
+
content_tag(
|
88
|
+
:div,
|
89
|
+
id: id,
|
90
|
+
class: "offcanvas-body #{klass}",
|
91
|
+
data: data,
|
92
|
+
aria: aria,
|
93
|
+
&block
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
# @todo
|
98
|
+
#
|
99
|
+
#
|
100
|
+
def to_s
|
101
|
+
@data.merge!('bs-scroll': @scrollable)
|
102
|
+
@data.merge!('bs-backdrop': @backdrop)
|
103
|
+
|
104
|
+
content_tag(
|
105
|
+
:div,
|
106
|
+
id: @id,
|
107
|
+
class: "offcanvas offcanvas-#{@position} #{@class}",
|
108
|
+
tabindex: -1,
|
109
|
+
data: @data,
|
110
|
+
aria: { labelledby: 'offcanvasExampleLabel' }
|
111
|
+
) do
|
112
|
+
@content.call(self)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# Builds the `x` button normally used in the header.
|
119
|
+
#
|
120
|
+
# @return [String]
|
121
|
+
#
|
122
|
+
def xbutton
|
123
|
+
content_tag :span, '×'.html_safe, class: 'visually-hidden', aria: { hidden: true }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module Bootstrap5Helper
|
2
|
+
# Builds a Offcanvas component.
|
3
|
+
#
|
4
|
+
#
|
5
|
+
class Offcanvas < Component
|
6
|
+
# Class constructor
|
7
|
+
#
|
8
|
+
# @param [ActionView]
|
9
|
+
# @param [NilClass|Symbol|Hash] position_or_options - :start, :end, :top, :bottom
|
10
|
+
# @param [Hash] opts
|
11
|
+
# @option opts [String] :id
|
12
|
+
# @option opts [String] :class
|
13
|
+
# @option opts [Hash] :data
|
14
|
+
# @option opts [Hash] :aria
|
15
|
+
# @option opts [Boolean] :scrollable
|
16
|
+
# @option opts [Boolean|String] :backdrop - true, false, static
|
17
|
+
# @return [Offcanvas]
|
18
|
+
#
|
19
|
+
def initialize(template, position_or_options = nil, opts = {}, &block)
|
20
|
+
super(template)
|
21
|
+
@pos, args = parse_position_or_options(position_or_options, opts)
|
22
|
+
@id = args.fetch(:id, uuid)
|
23
|
+
@class = args.fetch(:class, '')
|
24
|
+
@data = args.fetch(:data, {})
|
25
|
+
@aria = args.fetch(:aria, {})
|
26
|
+
@scrollable = args.fetch(:scrollable, false)
|
27
|
+
@backdrop = args.fetch(:backdrop, true)
|
28
|
+
@content = block || proc { '' }
|
29
|
+
end
|
30
|
+
# rubocop:disable Metrics/MethodLength
|
31
|
+
|
32
|
+
# Creates a button element to act as the trigger for the offcanvas component.
|
33
|
+
#
|
34
|
+
# @param [NilClass|String|Hash] text_or_options
|
35
|
+
# @param [Hash] opts
|
36
|
+
# @option opts [String] :id
|
37
|
+
# @option opts [String] :class
|
38
|
+
# @option opts [Hash] :data
|
39
|
+
# @option opts [Hash] :aria
|
40
|
+
# @return [String]
|
41
|
+
#
|
42
|
+
def button(text_or_options = nil, opts = {}, &block)
|
43
|
+
text, args = parse_text_or_options(text_or_options, opts)
|
44
|
+
|
45
|
+
id = args.fetch(:id, nil)
|
46
|
+
klass = args.fetch(:class, '')
|
47
|
+
|
48
|
+
data = args.fetch(:data, {}).merge!(
|
49
|
+
'bs-toggle' => 'offcanvas',
|
50
|
+
'bs-target' => "##{@id}"
|
51
|
+
)
|
52
|
+
|
53
|
+
aria = args.fetch(:aria, {}).merge!(
|
54
|
+
'controls' => @id
|
55
|
+
)
|
56
|
+
|
57
|
+
content_tag(
|
58
|
+
:button,
|
59
|
+
text,
|
60
|
+
type: :button,
|
61
|
+
id: id,
|
62
|
+
class: klass,
|
63
|
+
data: data,
|
64
|
+
aria: aria,
|
65
|
+
&block
|
66
|
+
)
|
67
|
+
end
|
68
|
+
# rubocop:enable Metrics/MethodLength
|
69
|
+
|
70
|
+
# Creates a simple link to toggle the offcanvas content.
|
71
|
+
#
|
72
|
+
# @param [String|Hash|NilClass] text_or_options
|
73
|
+
# @param [Hash] opts
|
74
|
+
# @option opts [String] :id
|
75
|
+
# @option opts [String] :class
|
76
|
+
# @option opts [Hash] :data
|
77
|
+
# @option opts [Hash] :aria
|
78
|
+
# @return [String]
|
79
|
+
#
|
80
|
+
def link(text_or_options = nil, opts = {}, &block)
|
81
|
+
text, args = parse_text_or_options(text_or_options, opts)
|
82
|
+
args[:data] = args.fetch(:data, {}).merge!({ 'bs-toggle': 'offcanvas' })
|
83
|
+
args[:aria] = args.fetch(:aria, {}).merge!({ 'controls': @id })
|
84
|
+
|
85
|
+
options = ["##{@id}", args]
|
86
|
+
options.prepend(text) if text.present?
|
87
|
+
|
88
|
+
@template.link_to(*options, &block)
|
89
|
+
end
|
90
|
+
|
91
|
+
# rubocop:disable Metrics/MethodLength
|
92
|
+
|
93
|
+
# Used to make a custom dom element for a trigger. Use this when the
|
94
|
+
# trigger isnt a link or button.
|
95
|
+
#
|
96
|
+
# @param [Symbol|Hash|NilClass] tag_or_options
|
97
|
+
# @param [Hash] opts
|
98
|
+
# @option opts [String] :id
|
99
|
+
# @option opts [String] :class
|
100
|
+
# @option opts [Hash] :data
|
101
|
+
# @option opts [Hash] :aria
|
102
|
+
# @return [String]
|
103
|
+
#
|
104
|
+
def trigger(tag_or_options = nil, opts = {}, &block)
|
105
|
+
tag, args = parse_tag_or_options(tag_or_options, opts)
|
106
|
+
|
107
|
+
id = args.fetch(:id, nil)
|
108
|
+
klass = args.fetch(:class, '')
|
109
|
+
aria = args.fetch(:aria, {}).merge!({ 'controls': @id })
|
110
|
+
data = args.fetch(:data, {}).merge!(
|
111
|
+
'bs-toggle': 'offcanvas',
|
112
|
+
'bs-target': "##{@id}"
|
113
|
+
)
|
114
|
+
|
115
|
+
content_tag(
|
116
|
+
tag || config({ offcanvas: :trigger }, :div),
|
117
|
+
id: id,
|
118
|
+
class: klass,
|
119
|
+
data: data,
|
120
|
+
aria: aria,
|
121
|
+
&block
|
122
|
+
)
|
123
|
+
end
|
124
|
+
# rubocop:enable Metrics/MethodLength
|
125
|
+
|
126
|
+
# Used to generate the main component. This class serves as a wrapper, so
|
127
|
+
# that buttons and links have reference to the content component.
|
128
|
+
#
|
129
|
+
# @return [String]
|
130
|
+
#
|
131
|
+
def content(&block)
|
132
|
+
Content.new(
|
133
|
+
@template,
|
134
|
+
{
|
135
|
+
id: @id,
|
136
|
+
class: @class,
|
137
|
+
data: @data,
|
138
|
+
aria: @aria,
|
139
|
+
scrollable: @scrollable,
|
140
|
+
backdrop: @backdrop,
|
141
|
+
position: @pos
|
142
|
+
},
|
143
|
+
&block
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Renders the component as a String, but only to the output bugger.
|
148
|
+
#
|
149
|
+
# @return [NilClass]
|
150
|
+
#
|
151
|
+
def to_s
|
152
|
+
@content.call(self)
|
153
|
+
|
154
|
+
nil
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
# Because this options parser is only going to be used here, I figured
|
160
|
+
# we would just define it here. That way other components don't have
|
161
|
+
# to inherit it.
|
162
|
+
#
|
163
|
+
# @return [Array]
|
164
|
+
#
|
165
|
+
def parse_position_or_options(*args)
|
166
|
+
parse_arguments(*args, 'start')
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Bootstrap5Helper
|
2
|
+
# Builds a simple CSS spinner component.
|
3
|
+
#
|
4
|
+
#
|
5
|
+
class PageHeader < Component
|
6
|
+
# Class constructor
|
7
|
+
#
|
8
|
+
# @param [ActionView] template
|
9
|
+
# @param [Symbol|Hash] tag_or_options
|
10
|
+
# @param [Hash] opts
|
11
|
+
# @option opts [String] :id
|
12
|
+
# @option opts [String] :class
|
13
|
+
# @option opts [Hash] :data
|
14
|
+
#
|
15
|
+
def initialize(template, tag_or_options = nil, opts = {}, &block)
|
16
|
+
super(template)
|
17
|
+
|
18
|
+
@tag, args = parse_tag_or_options(tag_or_options, opts)
|
19
|
+
@tag ||= config(:page_header, :h1)
|
20
|
+
|
21
|
+
@id = args.fetch(:id, uuid)
|
22
|
+
@class = args.fetch(:class, '')
|
23
|
+
@data = args.fetch(:data, {})
|
24
|
+
@content = block || proc { '' }
|
25
|
+
end
|
26
|
+
|
27
|
+
# String representation of the object.
|
28
|
+
#
|
29
|
+
# @return [String]
|
30
|
+
#
|
31
|
+
def to_s
|
32
|
+
content_tag(
|
33
|
+
@tag,
|
34
|
+
id: @id,
|
35
|
+
class: "pb-2 mt-4 mb-2 border-bottom #{@class}",
|
36
|
+
data: @data
|
37
|
+
) do
|
38
|
+
@content.call(self)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Bootstrap5Helper
|
2
|
+
# Simple Railtie to hook out module into ActionView.
|
3
|
+
#
|
4
|
+
#
|
5
|
+
class Railtie < ::Rails::Railtie
|
6
|
+
config.after_initialize do
|
7
|
+
ActiveSupport.on_load(:action_view) do
|
8
|
+
include Bootstrap5Helper if Bootstrap5Helper.config.autoload_in_views?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Bootstrap5Helper
|
2
|
+
# Builds a simple CSS spinner component.
|
3
|
+
#
|
4
|
+
#
|
5
|
+
class Spinner < Component
|
6
|
+
# Class constructor
|
7
|
+
#
|
8
|
+
# @note The different support types are: `:border` and `:grow`
|
9
|
+
#
|
10
|
+
# @param [ActionView] template
|
11
|
+
# @param [Hash] opts
|
12
|
+
# @option opts [Symbol] :type
|
13
|
+
# @option opts [String] :id
|
14
|
+
# @option opts [String] :class
|
15
|
+
# @option opts [Hash] :data
|
16
|
+
#
|
17
|
+
def initialize(template, opts = {}, &block)
|
18
|
+
super(template)
|
19
|
+
|
20
|
+
@type = opts.fetch(:type, :border)
|
21
|
+
@id = opts.fetch(:id, uuid)
|
22
|
+
@class = opts.fetch(:class, '')
|
23
|
+
@data = opts.fetch(:data, {})
|
24
|
+
@content = block || proc { '' }
|
25
|
+
end
|
26
|
+
|
27
|
+
# String representation of the object.
|
28
|
+
#
|
29
|
+
# @return [String]
|
30
|
+
#
|
31
|
+
def to_s
|
32
|
+
content_tag(
|
33
|
+
:span,
|
34
|
+
id: @id,
|
35
|
+
class: "spinner-#{@type} #{@class}",
|
36
|
+
role: 'status',
|
37
|
+
aria: { hidden: true },
|
38
|
+
data: @data
|
39
|
+
) do
|
40
|
+
content_tag :span, 'Loading', class: 'visually-hidden'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Bootstrap5Helper
|
2
|
+
class Tab
|
3
|
+
# Build a Content component to be used with Tabs
|
4
|
+
#
|
5
|
+
#
|
6
|
+
class Content < Component
|
7
|
+
# Class constructor
|
8
|
+
#
|
9
|
+
# @param [ActionView] template
|
10
|
+
# @param [Hash] opts
|
11
|
+
# @option opts [String] :id
|
12
|
+
# @option opts [String] :class
|
13
|
+
# @option opts [Hash] :data
|
14
|
+
#
|
15
|
+
def initialize(template, opts = {}, &block)
|
16
|
+
super(template)
|
17
|
+
|
18
|
+
@id = opts.fetch(:id, uuid)
|
19
|
+
@class = opts.fetch(:class, '')
|
20
|
+
@data = opts.fetch(:data, {})
|
21
|
+
@content = block || proc { '' }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Builds the pane for the tab.
|
25
|
+
#
|
26
|
+
# @param [Symbol] source
|
27
|
+
# @param [Hash] opts
|
28
|
+
# @option opts [String] :class
|
29
|
+
# @option opts [Hash] :data
|
30
|
+
# @return [String]
|
31
|
+
#
|
32
|
+
def pane(source, opts = {}, &block)
|
33
|
+
id = opts.fetch(:id, source)
|
34
|
+
klass = opts.fetch(:class, '')
|
35
|
+
data = opts.fetch(:data, {})
|
36
|
+
|
37
|
+
content_tag(
|
38
|
+
:div,
|
39
|
+
id: id,
|
40
|
+
class: "tab-pane #{klass}",
|
41
|
+
role: 'tabpanel',
|
42
|
+
data: data,
|
43
|
+
&block
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
# String representation of the object.
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
#
|
51
|
+
def to_s
|
52
|
+
content_tag :div, id: @id, class: "tab-content #{@class}" do
|
53
|
+
@content.call(self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Bootstrap5Helper
|
2
|
+
# Builds a Tab component.
|
3
|
+
#
|
4
|
+
#
|
5
|
+
class Tab < Component
|
6
|
+
# Class constructor
|
7
|
+
#
|
8
|
+
# @param [ActionView] template
|
9
|
+
# @param [Symbol|String|Hash] type_or_options
|
10
|
+
# @param [Hash] opts
|
11
|
+
# @option opts [String] :id
|
12
|
+
# @option opts [String] :class
|
13
|
+
# @option opts [Hash] :data
|
14
|
+
#
|
15
|
+
def initialize(template, type_or_options = nil, opts = {}, &block)
|
16
|
+
super(template)
|
17
|
+
@type, args = type_or_options(type_or_options, opts)
|
18
|
+
|
19
|
+
@id = args.fetch(:id, uuid)
|
20
|
+
@class = args.fetch(:class, '')
|
21
|
+
@data = args.fetch(:data, {})
|
22
|
+
@content = block || proc { '' }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Builds a custom Nav component for the tabs.
|
26
|
+
#
|
27
|
+
# @param [Symbol|Hash] tag_or_options
|
28
|
+
# @param [Hash] opts
|
29
|
+
# @option opts [String] :class
|
30
|
+
# @option opts [Hash] :data
|
31
|
+
# @return [Nav]
|
32
|
+
#
|
33
|
+
def nav(tag_or_options = nil, opts = {}, &block)
|
34
|
+
tag, args = parse_tag_or_options(tag_or_options, opts)
|
35
|
+
|
36
|
+
args[:class] = (args[:class] || '') << " nav-#{@type}"
|
37
|
+
args[:data] = (args[:data] || {}).merge('bs-toggle' => 'tab')
|
38
|
+
args[:child] = { data: { 'bs-toggle' => 'tab' } }
|
39
|
+
|
40
|
+
Nav.new(@template, tag, args, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Builds the Content object for the Tab.
|
44
|
+
#
|
45
|
+
# @param [Hash] opts
|
46
|
+
# @option opts [String] :id
|
47
|
+
# @option opts [String] :class
|
48
|
+
# @option opts [Hash] :data
|
49
|
+
# @return [Tab::Content]
|
50
|
+
#
|
51
|
+
def content(opts = {}, &block)
|
52
|
+
Content.new(@template, opts, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @note This has a weird interaction. Because this object doesn't actually return any wrapping
|
56
|
+
# string or DOM element, we want to return nil, so that only the output buffer on the sub components are
|
57
|
+
# returned.
|
58
|
+
#
|
59
|
+
# If we return the return value of the block, we will get the last element added to the input
|
60
|
+
# buffer as an unescaped string.
|
61
|
+
#
|
62
|
+
def to_s
|
63
|
+
@content.call(self)
|
64
|
+
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def type_or_options(*args)
|
69
|
+
parse_arguments(*args, :tabs)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|