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.
@@ -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, '&times;'.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
@@ -0,0 +1,7 @@
1
+ module Bootstrap5Helper
2
+ # Builds a Toast component.
3
+ #
4
+ #
5
+ class Toast < Component
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Bootstrap5Helper
2
+ VERSION = '1.0.0'.freeze
3
+ end