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