element_component 0.6.0 → 0.8.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 +4 -4
- data/AGENTS.md +5 -5
- data/README.md +348 -41
- data/examples/alert_example.rb +17 -17
- data/examples/badge_example.rb +63 -0
- data/examples/breadcrumb_example.rb +40 -0
- data/examples/button_example.rb +86 -0
- data/examples/button_group_example.rb +55 -0
- data/examples/card_example.rb +62 -0
- data/examples/carousel_example.rb +97 -0
- data/examples/close_button_example.rb +40 -0
- data/examples/dropdown_example.rb +131 -0
- data/examples/list_group_example.rb +68 -0
- data/examples/modal_example.rb +122 -0
- data/examples/nav_example.rb +77 -0
- data/examples/navbar_example.rb +104 -0
- data/examples/pagination_example.rb +47 -0
- data/examples/progress_example.rb +54 -0
- data/examples/spinner_example.rb +49 -0
- data/examples/table_example.rb +41 -0
- data/lib/element_component/components/alert.rb +3 -3
- data/lib/element_component/components/badge.rb +18 -0
- data/lib/element_component/components/breadcrumb/item.rb +29 -0
- data/lib/element_component/components/breadcrumb.rb +26 -0
- data/lib/element_component/components/button.rb +25 -0
- data/lib/element_component/components/button_group.rb +18 -0
- data/lib/element_component/components/card/body.rb +14 -0
- data/lib/element_component/components/card/footer.rb +14 -0
- data/lib/element_component/components/card/header.rb +14 -0
- data/lib/element_component/components/card/image.rb +17 -0
- data/lib/element_component/components/card/text.rb +14 -0
- data/lib/element_component/components/card/title.rb +14 -0
- data/lib/element_component/components/card.rb +21 -0
- data/lib/element_component/components/carousel/caption.rb +14 -0
- data/lib/element_component/components/carousel/item.rb +16 -0
- data/lib/element_component/components/carousel.rb +70 -0
- data/lib/element_component/components/close_button.rb +17 -0
- data/lib/element_component/components/dropdown/divider.rb +20 -0
- data/lib/element_component/components/dropdown/header.rb +22 -0
- data/lib/element_component/components/dropdown/item.rb +33 -0
- data/lib/element_component/components/dropdown/menu.rb +15 -0
- data/lib/element_component/components/dropdown.rb +43 -0
- data/lib/element_component/components/list_group/item.rb +23 -0
- data/lib/element_component/components/list_group.rb +18 -0
- data/lib/element_component/components/modal/body.rb +14 -0
- data/lib/element_component/components/modal/content.rb +14 -0
- data/lib/element_component/components/modal/dialog.rb +21 -0
- data/lib/element_component/components/modal/footer.rb +14 -0
- data/lib/element_component/components/modal/header.rb +16 -0
- data/lib/element_component/components/modal/title.rb +14 -0
- data/lib/element_component/components/modal.rb +42 -0
- data/lib/element_component/components/nav/item.rb +14 -0
- data/lib/element_component/components/nav/link.rb +18 -0
- data/lib/element_component/components/nav.rb +23 -0
- data/lib/element_component/components/navbar/brand.rb +15 -0
- data/lib/element_component/components/navbar/collapse.rb +16 -0
- data/lib/element_component/components/navbar/nav.rb +14 -0
- data/lib/element_component/components/navbar/toggler.rb +22 -0
- data/lib/element_component/components/navbar.rb +51 -0
- data/lib/element_component/components/pagination/item.rb +27 -0
- data/lib/element_component/components/pagination.rb +33 -0
- data/lib/element_component/components/progress/bar.rb +24 -0
- data/lib/element_component/components/progress.rb +17 -0
- data/lib/element_component/components/spinner.rb +19 -0
- data/lib/element_component/components/table.rb +21 -0
- data/lib/element_component/components.rb +16 -0
- data/lib/element_component/element.rb +5 -3
- data/lib/element_component/version.rb +1 -1
- metadata +61 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9cbfcfb824fb827c1bd03d08c5b2d0eec50fefa7b06c392b3e3393ce07c445be
|
|
4
|
+
data.tar.gz: ef7c00a35e38318c158fd43397552d37559d62c6996ca2f9a1a0dd05c2e32fc7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f8297bcae5e86b8201fec505845b928dcf77bcfe4fc28d2d034c29b702446cf0e5566cc82468826b70173aa2392ea4c8b400b9eeeeeabad173923688ff4b5364
|
|
7
|
+
data.tar.gz: 934369f2c1d3a14b74437f6b71427ae1c92fa4ba3e27054f5755fe793e1442fa3c565783d96ce1cfa209a8cbbd35b5da338688910228240c291736ed9d24d46c
|
data/AGENTS.md
CHANGED
|
@@ -44,21 +44,21 @@ Components live under `ElementComponent::Components`. Each component folder cont
|
|
|
44
44
|
|
|
45
45
|
| Component | Class | Description |
|
|
46
46
|
|---|---|---|
|
|
47
|
-
| Alert | `Components::Alert` | `.alert` container with
|
|
47
|
+
| Alert | `Components::Alert` | `.alert` container with variant |
|
|
48
48
|
| Alert Heading | `Components::AlertHeading` | `.alert-heading` (`<h4>`) |
|
|
49
49
|
| Alert Link | `Components::AlertLink` | `.alert-link` (`<a>`) |
|
|
50
50
|
| Alert Close | `Components::AlertCloseButton` | `.btn-close` (self-closing `<button>`) |
|
|
51
51
|
|
|
52
|
-
**Alert constructor**: `Alert.new(
|
|
53
|
-
- `
|
|
52
|
+
**Alert constructor**: `Alert.new(variant: :primary, dismissible: false, **attributes, &block)`
|
|
53
|
+
- `variant`: one of `:primary`, `:secondary`, `:success`, `:danger`, `:warning`, `:info`, `:light`, `:dark`
|
|
54
54
|
- `dismissible`: adds `.alert-dismissible` class and appends a `CloseButton`
|
|
55
|
-
- `&block`:
|
|
55
|
+
- `&block`: block with element parameter for adding content inside the element
|
|
56
56
|
|
|
57
57
|
**Component guidelines**:
|
|
58
58
|
- Always use `add_attribute()` instead of manipulating the `attributes` hash directly
|
|
59
59
|
- Each class in its own file under a component-named folder
|
|
60
60
|
- Call `super("tag_name", closing_tag: ..., &block)` first, then chain `add_attribute` calls
|
|
61
|
-
- `
|
|
61
|
+
- `block.call(self)` lives in `Element#initialize` — passes element to block via block parameter
|
|
62
62
|
- Pass user attributes last via `add_attribute(attributes)`
|
|
63
63
|
|
|
64
64
|
### Sub-component example pattern:
|
data/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# ElementComponent
|
|
2
2
|
|
|
3
|
-
A lightweight and flexible HTML builder for Ruby. `ElementComponent` provides a simple, object-oriented way to construct HTML structures programmatically, with dynamic attribute management, content nesting, rendering hooks, and pre-built components.
|
|
3
|
+
A lightweight and flexible HTML builder for Ruby. `ElementComponent` provides a simple, object-oriented way to construct HTML structures programmatically, with dynamic attribute management, content nesting, rendering hooks, and a comprehensive set of pre-built Bootstrap 5 components.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Object-Oriented HTML Construction** — Build HTML trees using Ruby objects with dynamic attribute management
|
|
8
|
+
- **Block DSL** — Nest content inline with block parameters and the `new_element` helper
|
|
9
|
+
- **Rendering Hooks** — `before_render`, `after_render`, and `around_render` callbacks for dynamic content
|
|
10
|
+
- **17 Bootstrap 5 Components** — Ready-to-use Alert, Badge, Breadcrumb, Button, ButtonGroup, Card, Carousel, CloseButton, Dropdown, ListGroup, Modal, Nav, Navbar, Pagination, Progress, Spinner, and Table
|
|
11
|
+
- **Chained API** — All `add_*` methods return `self` for method chaining
|
|
12
|
+
- **Self-Closing Tags** — Support for void elements like `<img>`, `<input>`, `<br>`
|
|
4
13
|
|
|
5
14
|
## Installation
|
|
6
15
|
|
|
@@ -32,9 +41,9 @@ puts p.render
|
|
|
32
41
|
Use a block to add content inline:
|
|
33
42
|
|
|
34
43
|
```ruby
|
|
35
|
-
div = ElementComponent::Element.new("div", class: "container") do
|
|
36
|
-
add_content("Welcome")
|
|
37
|
-
add_content(ElementComponent::Element.new("h1") { add_content("Title") })
|
|
44
|
+
div = ElementComponent::Element.new("div", class: "container") do |e|
|
|
45
|
+
e.add_content("Welcome")
|
|
46
|
+
e.add_content(ElementComponent::Element.new("h1") { |h| h.add_content("Title") })
|
|
38
47
|
end
|
|
39
48
|
puts div.render
|
|
40
49
|
# => <div class="container">Welcome<h1>Title</h1></div>
|
|
@@ -45,9 +54,9 @@ puts div.render
|
|
|
45
54
|
Inside a block, use `new_element` as a shorthand:
|
|
46
55
|
|
|
47
56
|
```ruby
|
|
48
|
-
div = ElementComponent::Element.new("div") do
|
|
49
|
-
add_content(new_element("h1") { add_content("Hello") })
|
|
50
|
-
add_content(new_element("p", class: "lead") { add_content("World") })
|
|
57
|
+
div = ElementComponent::Element.new("div") do |e|
|
|
58
|
+
e.add_content(e.new_element("h1") { |h| h.add_content("Hello") })
|
|
59
|
+
e.add_content(e.new_element("p", class: "lead") { |p| p.add_content("World") })
|
|
51
60
|
end
|
|
52
61
|
puts div.render
|
|
53
62
|
# => <div><h1>Hello</h1><p class="lead">World</p></div>
|
|
@@ -64,10 +73,10 @@ div = ElementComponent::Element.new("div")
|
|
|
64
73
|
div.add_content("plain text")
|
|
65
74
|
|
|
66
75
|
# Element instance
|
|
67
|
-
div.add_content(ElementComponent::Element.new("span") { add_content("nested") })
|
|
76
|
+
div.add_content(ElementComponent::Element.new("span") { |s| s.add_content("nested") })
|
|
68
77
|
|
|
69
78
|
# Block (evaluated at render time, has access to new_element)
|
|
70
|
-
div.add_content { new_element("em") { add_content("deferred") } }
|
|
79
|
+
div.add_content { |e| e.new_element("em") { |em| em.add_content("deferred") } }
|
|
71
80
|
|
|
72
81
|
puts div.render
|
|
73
82
|
# => <div>plain text<span>nested</span><em>deferred</em></div>
|
|
@@ -111,59 +120,356 @@ puts div.render
|
|
|
111
120
|
# => <div class="dynamic">content</div>
|
|
112
121
|
```
|
|
113
122
|
|
|
114
|
-
## Pre-built Components
|
|
123
|
+
## Pre-built Components (Bootstrap 5)
|
|
124
|
+
|
|
125
|
+
All components live under `ElementComponent::Components` and support the block DSL, chained `add_content`, and custom HTML attributes via `**attributes`.
|
|
126
|
+
|
|
127
|
+
### Quick Reference
|
|
128
|
+
|
|
129
|
+
| Component | Class | Tag | Key Options |
|
|
130
|
+
|---|---|---|---|
|
|
131
|
+
| Alert | `Alert` | `<div>` | `variant`, `dismissible` |
|
|
132
|
+
| Badge | `Badge` | `<span>` | `variant`, `pill` |
|
|
133
|
+
| Breadcrumb | `Breadcrumb` | `<nav>` → `<ol>` | `BreadcrumbItem` (`href`, `active`) |
|
|
134
|
+
| Button | `Button` | `<button>` / `<a>` | `variant`, `outline`, `size`, `href` |
|
|
135
|
+
| ButtonGroup | `ButtonGroup` | `<div>` | `size`, `vertical` |
|
|
136
|
+
| Card | `Card` | `<div>` | Sub-components: Header, Body, Footer, Title, Text, Image |
|
|
137
|
+
| Carousel | `Carousel` | `<div>` | `fade`, `indicators`, `controls`; `CarouselItem`, `CarouselCaption` |
|
|
138
|
+
| CloseButton | `CloseButton` | `<button>` (self-closing) | `disabled` |
|
|
139
|
+
| Dropdown | `Dropdown` | `<div>` | `direction`; `DropdownMenu`, `DropdownItem`, `DropdownDivider`, `DropdownHeader` |
|
|
140
|
+
| ListGroup | `ListGroup` | `<ul>` | `flush`, `numbered`; `ListGroupItem` (`variant`, `active`, `disabled`, `href`) |
|
|
141
|
+
| Modal | `Modal` | `<div>` | `fade`, `static`, `scrollable`, `centered`, `size`; `ModalContent`, `ModalHeader`, `ModalBody`, `ModalFooter` |
|
|
142
|
+
| Nav | `Nav` | `<ul>` | `type` (tabs/pills/underline), `fill`, `justified`, `vertical` |
|
|
143
|
+
| Navbar | `Navbar` | `<nav>` | `expand`, `theme`, `background`, `fixed`, `sticky`; `NavbarBrand`, `NavbarNav`, `NavbarToggler` |
|
|
144
|
+
| Pagination | `Pagination` | `<nav>` → `<ul>` | `size`; `PageItem` (`active`, `disabled`) |
|
|
145
|
+
| Progress | `Progress` | `<div>` | `ProgressBar` (`value`, `variant`, `striped`, `animated`) |
|
|
146
|
+
| Spinner | `Spinner` | `<div>` | `type` (border/grow), `variant` |
|
|
147
|
+
| Table | `Table` | `<table>` | `striped`, `bordered`, `hover`, `small`, `variant` |
|
|
115
148
|
|
|
116
|
-
|
|
149
|
+
### Alert
|
|
117
150
|
|
|
118
|
-
### Alert (Bootstrap 5)
|
|
119
151
|
|
|
120
152
|
```ruby
|
|
121
|
-
alert = ElementComponent::Components::Alert.new(
|
|
122
|
-
add_content("Operation completed!")
|
|
153
|
+
alert = ElementComponent::Components::Alert.new(variant: :success) do |e|
|
|
154
|
+
e.add_content("Operation completed!")
|
|
123
155
|
end
|
|
124
|
-
puts alert.render
|
|
125
156
|
# => <div class="alert alert-success" role="alert">Operation completed!</div>
|
|
126
157
|
```
|
|
127
158
|
|
|
128
|
-
**
|
|
159
|
+
**Variants**: `:primary`, `:secondary`, `:success`, `:danger`, `:warning`, `:info`, `:light`, `:dark`
|
|
129
160
|
|
|
130
|
-
|
|
161
|
+
Dismissible alerts, headings, and links use sub-components:
|
|
131
162
|
|
|
132
163
|
```ruby
|
|
133
|
-
alert = ElementComponent::Components::Alert.new(
|
|
134
|
-
add_content(
|
|
164
|
+
alert = ElementComponent::Components::Alert.new(variant: :warning, dismissible: true) do |e|
|
|
165
|
+
e.add_content(ElementComponent::Components::AlertHeading.new { |h| h.add_content("Warning") })
|
|
166
|
+
e.add_content("Please review. ")
|
|
167
|
+
e.add_content(ElementComponent::Components::AlertLink.new(href: "/details") { |l| l.add_content("Details") })
|
|
135
168
|
end
|
|
136
|
-
puts alert.render
|
|
137
169
|
# => <div class="alert alert-warning alert-dismissible" role="alert">
|
|
138
|
-
#
|
|
170
|
+
# <h4 class="alert-heading">Warning</h4>
|
|
171
|
+
# Please review.
|
|
172
|
+
# <a class="alert-link" href="/details">Details</a>
|
|
139
173
|
# <button class="btn-close" data-bs-dismiss="alert" aria-label="Close">
|
|
140
174
|
# </div>
|
|
141
175
|
```
|
|
142
176
|
|
|
143
|
-
|
|
177
|
+
**Sub-components**:
|
|
178
|
+
|
|
179
|
+
| Class | Tag | CSS Class |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `AlertHeading` | `<h4>` | `.alert-heading` |
|
|
182
|
+
| `AlertLink` | `<a>` | `.alert-link` |
|
|
183
|
+
| `AlertCloseButton` | `<button>` (self-closing) | `.btn-close` |
|
|
184
|
+
|
|
185
|
+
### Button
|
|
186
|
+
|
|
144
187
|
|
|
145
188
|
```ruby
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
189
|
+
# Standard button
|
|
190
|
+
btn = ElementComponent::Components::Button.new(variant: :primary) { |b| b.add_content("Click") }
|
|
191
|
+
# => <button class="btn btn-primary" type="button">Click</button>
|
|
192
|
+
|
|
193
|
+
# Outline variant
|
|
194
|
+
btn = ElementComponent::Components::Button.new(variant: :danger, outline: true) { |b| b.add_content("Delete") }
|
|
195
|
+
# => <button class="btn btn-outline-danger" type="button">Delete</button>
|
|
196
|
+
|
|
197
|
+
# As a link
|
|
198
|
+
btn = ElementComponent::Components::Button.new(variant: :primary, href: "/home") { |b| b.add_content("Home") }
|
|
199
|
+
# => <a class="btn btn-primary" href="/home">Home</a>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Options**: `variant` (primary/secondary/success/danger/warning/info/light/dark/link), `outline`, `size` (sm/lg), `href`
|
|
203
|
+
|
|
204
|
+
### Badge
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
badge = ElementComponent::Components::Badge.new(variant: :primary) { |b| b.add_content("New") }
|
|
209
|
+
# => <span class="badge bg-primary">New</span>
|
|
210
|
+
|
|
211
|
+
pill = ElementComponent::Components::Badge.new(variant: :danger, pill: true) { |b| b.add_content("99+") }
|
|
212
|
+
# => <span class="badge bg-danger rounded-pill">99+</span>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Card
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
card = ElementComponent::Components::Card.new do |c|
|
|
220
|
+
c.add_content(ElementComponent::Components::CardImage.new(src: "photo.jpg", top: true))
|
|
221
|
+
c.add_content(ElementComponent::Components::CardBody.new do |body|
|
|
222
|
+
body.add_content(ElementComponent::Components::CardTitle.new { |t| t.add_content("Title") })
|
|
223
|
+
body.add_content(ElementComponent::Components::CardText.new { |t| t.add_content("Some text.") })
|
|
224
|
+
end)
|
|
225
|
+
c.add_content(ElementComponent::Components::CardFooter.new { |f| f.add_content("Footer") })
|
|
150
226
|
end
|
|
151
|
-
puts alert.render
|
|
152
|
-
# => <div class="alert alert-info" role="alert">
|
|
153
|
-
# <h4 class="alert-heading">Notice</h4>
|
|
154
|
-
# Please review.
|
|
155
|
-
# <a class="alert-link" href="/details">Details</a>
|
|
156
|
-
# </div>
|
|
157
227
|
```
|
|
158
228
|
|
|
159
|
-
|
|
229
|
+
**Sub-components**:
|
|
160
230
|
|
|
161
|
-
| Class | Tag | Class |
|
|
231
|
+
| Class | Tag | CSS Class |
|
|
162
232
|
|---|---|---|
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
233
|
+
| `CardHeader` | `<div>` | `.card-header` |
|
|
234
|
+
| `CardBody` | `<div>` | `.card-body` |
|
|
235
|
+
| `CardFooter` | `<div>` | `.card-footer` |
|
|
236
|
+
| `CardTitle` | `<h5>` | `.card-title` |
|
|
237
|
+
| `CardText` | `<p>` | `.card-text` |
|
|
238
|
+
| `CardImage` | `<img>` (self-closing) | `.card-img[-top\|-bottom]` |
|
|
239
|
+
|
|
240
|
+
### Nav
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
nav = ElementComponent::Components::Nav.new(type: :tabs) do |n|
|
|
245
|
+
n.add_content(ElementComponent::Components::NavItem.new do |item|
|
|
246
|
+
item.add_content(ElementComponent::Components::NavLink.new(href: "/", active: true) { |l| l.add_content("Home") })
|
|
247
|
+
end)
|
|
248
|
+
n.add_content(ElementComponent::Components::NavItem.new do |item|
|
|
249
|
+
item.add_content(ElementComponent::Components::NavLink.new(href: "/profile") { |l| l.add_content("Profile") })
|
|
250
|
+
end)
|
|
251
|
+
end
|
|
252
|
+
# => <ul class="nav nav-tabs">...</ul>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Options**: `type` (tabs/pills/underline), `fill`, `justified`, `vertical`
|
|
256
|
+
|
|
257
|
+
### Breadcrumb
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
crumb = ElementComponent::Components::Breadcrumb.new do |b|
|
|
262
|
+
b.add_content(ElementComponent::Components::BreadcrumbItem.new(href: "/") { |i| i.add_content("Home") })
|
|
263
|
+
b.add_content(ElementComponent::Components::BreadcrumbItem.new(href: "/section") { |i| i.add_content("Section") })
|
|
264
|
+
b.add_content(ElementComponent::Components::BreadcrumbItem.new(active: true) { |i| i.add_content("Current") })
|
|
265
|
+
end
|
|
266
|
+
# => <nav aria-label="breadcrumb"><ol class="breadcrumb">...</ol></nav>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### ListGroup
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
```ruby
|
|
273
|
+
list = ElementComponent::Components::ListGroup.new(flush: true) do |l|
|
|
274
|
+
l.add_content(ElementComponent::Components::ListGroupItem.new { |i| i.add_content("Item 1") })
|
|
275
|
+
l.add_content(ElementComponent::Components::ListGroupItem.new(active: true) { |i| i.add_content("Item 2") })
|
|
276
|
+
l.add_content(ElementComponent::Components::ListGroupItem.new(href: "/link") { |i| i.add_content("Link") })
|
|
277
|
+
end
|
|
278
|
+
# => <ul class="list-group list-group-flush">...</ul>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Options**: `flush`, `numbered`; `ListGroupItem` options: `variant`, `active`, `disabled`, `href`
|
|
282
|
+
|
|
283
|
+
### Progress
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
progress = ElementComponent::Components::Progress.new do |p|
|
|
288
|
+
p.add_content(ElementComponent::Components::ProgressBar.new(value: 75, variant: :success, striped: true) do |bar|
|
|
289
|
+
bar.add_content("75%")
|
|
290
|
+
end)
|
|
291
|
+
end
|
|
292
|
+
# => <div class="progress" role="progressbar"><div class="progress-bar bg-success progress-bar-striped" ...>75%</div></div>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Spinner
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
border = ElementComponent::Components::Spinner.new(type: :border, variant: :primary)
|
|
300
|
+
# => <div class="spinner-border text-primary" role="status"></div>
|
|
301
|
+
|
|
302
|
+
grow = ElementComponent::Components::Spinner.new(type: :grow, variant: :success)
|
|
303
|
+
# => <div class="spinner-grow text-success" role="status"></div>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Table
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
table = ElementComponent::Components::Table.new(striped: true, bordered: true, hover: true) do |t|
|
|
311
|
+
t.add_content("<thead><tr><th>Name</th><th>Age</th></tr></thead>")
|
|
312
|
+
t.add_content("<tbody><tr><td>John</td><td>30</td></tr></tbody>")
|
|
313
|
+
end
|
|
314
|
+
# => <table class="table table-striped table-bordered table-hover">...</table>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Options**: `striped`, `bordered`, `hover`, `small`, `variant`
|
|
318
|
+
|
|
319
|
+
### Pagination
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
nav = ElementComponent::Components::Pagination.new(size: :lg) do |p|
|
|
324
|
+
p.add_content(ElementComponent::Components::PageItem.new(active: true) { |i| i.add_content("1") })
|
|
325
|
+
p.add_content(ElementComponent::Components::PageItem.new { |i| i.add_content("2") })
|
|
326
|
+
end
|
|
327
|
+
# => <nav aria-label="Pagination"><ul class="pagination pagination-lg">...</ul></nav>
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### ButtonGroup
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
group = ElementComponent::Components::ButtonGroup.new do |g|
|
|
335
|
+
g.add_content(ElementComponent::Components::Button.new(variant: :primary) { |b| b.add_content("Left") })
|
|
336
|
+
g.add_content(ElementComponent::Components::Button.new(variant: :primary) { |b| b.add_content("Middle") })
|
|
337
|
+
g.add_content(ElementComponent::Components::Button.new(variant: :primary) { |b| b.add_content("Right") })
|
|
338
|
+
end
|
|
339
|
+
# => <div class="btn-group" role="group">...</div>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Options**: `size` (sm/lg), `vertical`
|
|
343
|
+
|
|
344
|
+
### CloseButton
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
btn = ElementComponent::Components::CloseButton.new
|
|
349
|
+
# => <button class="btn-close" type="button" aria-label="Close">
|
|
350
|
+
|
|
351
|
+
disabled = ElementComponent::Components::CloseButton.new(disabled: true)
|
|
352
|
+
# => <button class="btn-close" type="button" aria-label="Close" disabled>
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Modal
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
```ruby
|
|
359
|
+
modal = ElementComponent::Components::Modal.new(id: "exampleModal") do |m|
|
|
360
|
+
m.add_content(ElementComponent::Components::ModalContent.new do |content|
|
|
361
|
+
content.add_content(ElementComponent::Components::ModalHeader.new do |header|
|
|
362
|
+
header.add_content(ElementComponent::Components::ModalTitle.new { |t| t.add_content("Modal title") })
|
|
363
|
+
end)
|
|
364
|
+
content.add_content(ElementComponent::Components::ModalBody.new { |body| body.add_content("Modal body text.") })
|
|
365
|
+
content.add_content(ElementComponent::Components::ModalFooter.new do |footer|
|
|
366
|
+
footer.add_content(ElementComponent::Components::Button.new(variant: :secondary) { |b| b.add_content("Close") })
|
|
367
|
+
footer.add_content(ElementComponent::Components::Button.new(variant: :primary) { |b| b.add_content("Save") })
|
|
368
|
+
end)
|
|
369
|
+
end)
|
|
370
|
+
end
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**Options**: `fade`, `static`, `scrollable`, `centered`, `size` (sm/lg/xl), `fullscreen`
|
|
374
|
+
|
|
375
|
+
**Sub-components**:
|
|
376
|
+
|
|
377
|
+
| Class | Tag | CSS Class |
|
|
378
|
+
|---|---|---|
|
|
379
|
+
| `ModalDialog` | `<div>` | `.modal-dialog` |
|
|
380
|
+
| `ModalContent` | `<div>` | `.modal-content` |
|
|
381
|
+
| `ModalHeader` | `<div>` | `.modal-header` |
|
|
382
|
+
| `ModalTitle` | `<h5>` | `.modal-title` |
|
|
383
|
+
| `ModalBody` | `<div>` | `.modal-body` |
|
|
384
|
+
| `ModalFooter` | `<div>` | `.modal-footer` |
|
|
385
|
+
|
|
386
|
+
### Carousel
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
```ruby
|
|
390
|
+
carousel = ElementComponent::Components::Carousel.new(id: "slides") do |c|
|
|
391
|
+
c.add_content(ElementComponent::Components::CarouselItem.new(active: true) do |item|
|
|
392
|
+
item.add_content(%(<img src="slide1.jpg" class="d-block w-100" alt="...">))
|
|
393
|
+
end)
|
|
394
|
+
c.add_content(ElementComponent::Components::CarouselItem.new do |item|
|
|
395
|
+
item.add_content(%(<img src="slide2.jpg" class="d-block w-100" alt="...">))
|
|
396
|
+
end)
|
|
397
|
+
end
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Options**: `fade` (crossfade), `indicators`, `controls`; indicators and navigation controls are auto-generated
|
|
401
|
+
|
|
402
|
+
**Sub-components**:
|
|
403
|
+
|
|
404
|
+
| Class | Tag | CSS Class |
|
|
405
|
+
|---|---|---|
|
|
406
|
+
| `CarouselItem` | `<div>` | `.carousel-item` |
|
|
407
|
+
| `CarouselCaption` | `<div>` | `.carousel-caption` |
|
|
408
|
+
|
|
409
|
+
### Dropdown
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
```ruby
|
|
413
|
+
dropdown = ElementComponent::Components::Dropdown.new do |d|
|
|
414
|
+
d.add_content(
|
|
415
|
+
ElementComponent::Element.new("button",
|
|
416
|
+
class: "btn btn-secondary dropdown-toggle",
|
|
417
|
+
type: "button",
|
|
418
|
+
"data-bs-toggle": "dropdown",
|
|
419
|
+
"aria-expanded": "false") { |b| b.add_content("Dropdown") }
|
|
420
|
+
)
|
|
421
|
+
d.add_content(
|
|
422
|
+
ElementComponent::Components::DropdownMenu.new do |menu|
|
|
423
|
+
menu.add_content(ElementComponent::Components::DropdownItem.new { |i| i.add_content("Action") })
|
|
424
|
+
menu.add_content(ElementComponent::Components::DropdownItem.new(active: true) { |i| i.add_content("Active") })
|
|
425
|
+
menu.add_content(ElementComponent::Components::DropdownDivider.new)
|
|
426
|
+
menu.add_content(ElementComponent::Components::DropdownItem.new(disabled: true) { |i| i.add_content("Disabled") })
|
|
427
|
+
end
|
|
428
|
+
)
|
|
429
|
+
end
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Options**: `direction` (dropup/dropend/dropstart)
|
|
433
|
+
|
|
434
|
+
**Sub-components**:
|
|
435
|
+
|
|
436
|
+
| Class | Tag | CSS Class |
|
|
437
|
+
|---|---|---|
|
|
438
|
+
| `DropdownMenu` | `<ul>` | `.dropdown-menu` |
|
|
439
|
+
| `DropdownItem` | `<li>` → `<a>`/`<button>` | `.dropdown-item` |
|
|
440
|
+
| `DropdownDivider` | `<li>` → `<hr>` | `.dropdown-divider` |
|
|
441
|
+
| `DropdownHeader` | `<li>` → `<h6>` | `.dropdown-header` |
|
|
442
|
+
|
|
443
|
+
### Navbar
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
```ruby
|
|
447
|
+
navbar = ElementComponent::Components::Navbar.new(theme: :dark, background: :dark) do |n|
|
|
448
|
+
n.add_content(ElementComponent::Components::NavbarBrand.new(href: "/") { |b| b.add_content("Brand") })
|
|
449
|
+
n.add_content(ElementComponent::Components::NavbarToggler.new(target: "nav"))
|
|
450
|
+
n.add_content(ElementComponent::Components::NavbarCollapse.new(id: "nav") do |collapse|
|
|
451
|
+
collapse.add_content(ElementComponent::Components::NavbarNav.new do |nav|
|
|
452
|
+
nav.add_content(ElementComponent::Components::NavItem.new do |item|
|
|
453
|
+
item.add_content(ElementComponent::Components::NavLink.new(href: "/", active: true) { |l| l.add_content("Home") })
|
|
454
|
+
end)
|
|
455
|
+
nav.add_content(ElementComponent::Components::NavItem.new do |item|
|
|
456
|
+
item.add_content(ElementComponent::Components::NavLink.new(href: "/about") { |l| l.add_content("About") })
|
|
457
|
+
end)
|
|
458
|
+
end)
|
|
459
|
+
end)
|
|
460
|
+
end
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Options**: `expand` (sm/md/lg/xl/xxl), `theme` (light/dark), `background`, `fixed` (top/bottom), `sticky` (top/bottom), `container`
|
|
464
|
+
|
|
465
|
+
**Sub-components**:
|
|
466
|
+
|
|
467
|
+
| Class | Tag | CSS Class |
|
|
468
|
+
|---|---|---|
|
|
469
|
+
| `NavbarBrand` | `<a>` | `.navbar-brand` |
|
|
470
|
+
| `NavbarToggler` | `<button>` (self-closing) | `.navbar-toggler` |
|
|
471
|
+
| `NavbarCollapse` | `<div>` | `.collapse .navbar-collapse` |
|
|
472
|
+
| `NavbarNav` | `<ul>` | `.navbar-nav` |
|
|
167
473
|
|
|
168
474
|
## Development
|
|
169
475
|
|
|
@@ -174,6 +480,9 @@ bundle exec rubocop # Lint
|
|
|
174
480
|
bundle exec rake # Spec + RuboCop
|
|
175
481
|
bin/console # Interactive console
|
|
176
482
|
ruby examples/alert_example.rb # Run Alert examples
|
|
483
|
+
ruby examples/button_example.rb # Run Button examples
|
|
484
|
+
ruby examples/card_example.rb # Run Card examples
|
|
485
|
+
# See all examples in the examples/ directory
|
|
177
486
|
```
|
|
178
487
|
|
|
179
488
|
### Test Coverage
|
|
@@ -196,9 +505,7 @@ Or push a version tag (e.g., `v0.6.0`) to trigger the automated release workflow
|
|
|
196
505
|
## Roadmap
|
|
197
506
|
|
|
198
507
|
- [ ] Support for Caching
|
|
199
|
-
- [
|
|
200
|
-
- [ ] Pre-built Bootstrap components (more)
|
|
201
|
-
- [ ] Enhanced DSL for nested structures
|
|
508
|
+
- [x] Pre-built Bootstrap components (Alert, Badge, Breadcrumb, Button, ButtonGroup, Card, Carousel, CloseButton, Dropdown, ListGroup, Modal, Nav, Navbar, Pagination, Progress, Spinner, Table)
|
|
202
509
|
|
|
203
510
|
## Contributing
|
|
204
511
|
|
data/examples/alert_example.rb
CHANGED
|
@@ -5,19 +5,19 @@ require_relative "../lib/element_component"
|
|
|
5
5
|
# =============================================================================
|
|
6
6
|
# Basic Alert
|
|
7
7
|
# =============================================================================
|
|
8
|
-
alert = ElementComponent::Components::Alert.new(
|
|
8
|
+
alert = ElementComponent::Components::Alert.new(variant: :success)
|
|
9
9
|
alert.add_content("Operation completed successfully!")
|
|
10
10
|
puts "=== Basic Alert ==="
|
|
11
11
|
puts alert.render
|
|
12
12
|
puts
|
|
13
13
|
|
|
14
14
|
# =============================================================================
|
|
15
|
-
# All
|
|
15
|
+
# All Variants
|
|
16
16
|
# =============================================================================
|
|
17
17
|
puts "=== All Alert Contexts ==="
|
|
18
|
-
ElementComponent::Components::Alert::
|
|
19
|
-
alert = ElementComponent::Components::Alert.new(
|
|
20
|
-
alert.add_content("#{
|
|
18
|
+
ElementComponent::Components::Alert::VALID_VARIANTS.each do |variant|
|
|
19
|
+
alert = ElementComponent::Components::Alert.new(variant: variant)
|
|
20
|
+
alert.add_content("#{variant.capitalize} alert message here.")
|
|
21
21
|
puts alert.render
|
|
22
22
|
end
|
|
23
23
|
puts
|
|
@@ -25,8 +25,8 @@ puts
|
|
|
25
25
|
# =============================================================================
|
|
26
26
|
# Dismissible Alert (use block DSL so close button is always last)
|
|
27
27
|
# =============================================================================
|
|
28
|
-
alert = ElementComponent::Components::Alert.new(
|
|
29
|
-
add_content("This alert can be dismissed.")
|
|
28
|
+
alert = ElementComponent::Components::Alert.new(variant: :warning, dismissible: true) do |e|
|
|
29
|
+
e.add_content("This alert can be dismissed.")
|
|
30
30
|
end
|
|
31
31
|
puts "=== Dismissible Alert ==="
|
|
32
32
|
puts alert.render
|
|
@@ -36,7 +36,7 @@ puts
|
|
|
36
36
|
# Alert with Custom Attributes
|
|
37
37
|
# =============================================================================
|
|
38
38
|
alert = ElementComponent::Components::Alert.new(
|
|
39
|
-
|
|
39
|
+
variant: :primary,
|
|
40
40
|
id: "main-alert",
|
|
41
41
|
style: "margin-top: 20px;"
|
|
42
42
|
)
|
|
@@ -48,10 +48,10 @@ puts
|
|
|
48
48
|
# =============================================================================
|
|
49
49
|
# Alert with Heading and Link (block DSL)
|
|
50
50
|
# =============================================================================
|
|
51
|
-
alert = ElementComponent::Components::Alert.new(
|
|
52
|
-
add_content(ElementComponent::Components::AlertHeading.new.tap { |h| h.add_content("Information") })
|
|
53
|
-
add_content("This is an important notice. ")
|
|
54
|
-
add_content(ElementComponent::Components::AlertLink.new(href: "/details").tap { |l| l.add_content("View details") })
|
|
51
|
+
alert = ElementComponent::Components::Alert.new(variant: :info) do |e|
|
|
52
|
+
e.add_content(ElementComponent::Components::AlertHeading.new.tap { |h| h.add_content("Information") })
|
|
53
|
+
e.add_content("This is an important notice. ")
|
|
54
|
+
e.add_content(ElementComponent::Components::AlertLink.new(href: "/details").tap { |l| l.add_content("View details") })
|
|
55
55
|
end
|
|
56
56
|
puts "=== Alert with Heading and Link ==="
|
|
57
57
|
puts alert.render
|
|
@@ -60,10 +60,10 @@ puts
|
|
|
60
60
|
# =============================================================================
|
|
61
61
|
# Dismissible Alert with Heading and Link
|
|
62
62
|
# =============================================================================
|
|
63
|
-
alert = ElementComponent::Components::Alert.new(
|
|
64
|
-
add_content(ElementComponent::Components::AlertHeading.new.tap { |h| h.add_content("Error!") })
|
|
65
|
-
add_content("Something went wrong. ")
|
|
66
|
-
add_content(ElementComponent::Components::AlertLink.new(href: "/support").tap do |l|
|
|
63
|
+
alert = ElementComponent::Components::Alert.new(variant: :danger, dismissible: true) do |e|
|
|
64
|
+
e.add_content(ElementComponent::Components::AlertHeading.new.tap { |h| h.add_content("Error!") })
|
|
65
|
+
e.add_content("Something went wrong. ")
|
|
66
|
+
e.add_content(ElementComponent::Components::AlertLink.new(href: "/support").tap do |l|
|
|
67
67
|
l.add_content("Contact support")
|
|
68
68
|
end)
|
|
69
69
|
end
|
|
@@ -74,7 +74,7 @@ puts
|
|
|
74
74
|
# =============================================================================
|
|
75
75
|
# Chained API Usage
|
|
76
76
|
# =============================================================================
|
|
77
|
-
alert = ElementComponent::Components::Alert.new(
|
|
77
|
+
alert = ElementComponent::Components::Alert.new(variant: :secondary)
|
|
78
78
|
.add_content("This alert was built using chained calls.")
|
|
79
79
|
puts "=== Chained API ==="
|
|
80
80
|
puts alert.render
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/element_component"
|
|
4
|
+
|
|
5
|
+
# =============================================================================
|
|
6
|
+
# Basic Badge
|
|
7
|
+
# =============================================================================
|
|
8
|
+
badge = ElementComponent::Components::Badge.new(variant: :primary)
|
|
9
|
+
badge.add_content("New")
|
|
10
|
+
puts "=== Basic Badge ==="
|
|
11
|
+
puts badge.render
|
|
12
|
+
puts
|
|
13
|
+
|
|
14
|
+
# =============================================================================
|
|
15
|
+
# Pill Badge
|
|
16
|
+
# =============================================================================
|
|
17
|
+
badge = ElementComponent::Components::Badge.new(variant: :danger, pill: true)
|
|
18
|
+
badge.add_content("99+")
|
|
19
|
+
puts "=== Pill Badge ==="
|
|
20
|
+
puts badge.render
|
|
21
|
+
puts
|
|
22
|
+
|
|
23
|
+
# =============================================================================
|
|
24
|
+
# All Contexts
|
|
25
|
+
# =============================================================================
|
|
26
|
+
puts "=== All Badge Variants ==="
|
|
27
|
+
ElementComponent::Components::Badge::VALID_VARIANTS.each do |variant|
|
|
28
|
+
badge = ElementComponent::Components::Badge.new(variant: variant)
|
|
29
|
+
badge.add_content(variant.to_s.capitalize)
|
|
30
|
+
puts badge.render
|
|
31
|
+
end
|
|
32
|
+
puts
|
|
33
|
+
|
|
34
|
+
# =============================================================================
|
|
35
|
+
# Badge with Block
|
|
36
|
+
# =============================================================================
|
|
37
|
+
badge = ElementComponent::Components::Badge.new(variant: :success) do |b|
|
|
38
|
+
b.add_content("Completed")
|
|
39
|
+
end
|
|
40
|
+
puts "=== Badge with Block ==="
|
|
41
|
+
puts badge.render
|
|
42
|
+
puts
|
|
43
|
+
|
|
44
|
+
# =============================================================================
|
|
45
|
+
# Chained API
|
|
46
|
+
# =============================================================================
|
|
47
|
+
badge = ElementComponent::Components::Badge.new(variant: :warning)
|
|
48
|
+
.add_content("Warning")
|
|
49
|
+
puts "=== Chained API ==="
|
|
50
|
+
puts badge.render
|
|
51
|
+
puts
|
|
52
|
+
|
|
53
|
+
# =============================================================================
|
|
54
|
+
# Badge with Custom Attributes
|
|
55
|
+
# =============================================================================
|
|
56
|
+
badge = ElementComponent::Components::Badge.new(
|
|
57
|
+
variant: :info,
|
|
58
|
+
id: "badge-1",
|
|
59
|
+
style: "font-size: 1.2em;"
|
|
60
|
+
)
|
|
61
|
+
badge.add_content("Custom")
|
|
62
|
+
puts "=== Badge with Custom Attributes ==="
|
|
63
|
+
puts badge.render
|