element_component 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +3 -3
  3. data/README.md +339 -32
  4. data/examples/alert_example.rb +10 -10
  5. data/examples/badge_example.rb +63 -0
  6. data/examples/breadcrumb_example.rb +36 -0
  7. data/examples/button_example.rb +86 -0
  8. data/examples/button_group_example.rb +55 -0
  9. data/examples/card_example.rb +62 -0
  10. data/examples/carousel_example.rb +95 -0
  11. data/examples/close_button_example.rb +40 -0
  12. data/examples/dropdown_example.rb +123 -0
  13. data/examples/list_group_example.rb +60 -0
  14. data/examples/modal_example.rb +120 -0
  15. data/examples/nav_example.rb +75 -0
  16. data/examples/navbar_example.rb +96 -0
  17. data/examples/pagination_example.rb +47 -0
  18. data/examples/progress_example.rb +54 -0
  19. data/examples/spinner_example.rb +49 -0
  20. data/examples/table_example.rb +41 -0
  21. data/lib/element_component/components/alert.rb +3 -3
  22. data/lib/element_component/components/badge.rb +18 -0
  23. data/lib/element_component/components/breadcrumb/item.rb +29 -0
  24. data/lib/element_component/components/breadcrumb.rb +26 -0
  25. data/lib/element_component/components/button.rb +25 -0
  26. data/lib/element_component/components/button_group.rb +18 -0
  27. data/lib/element_component/components/card/body.rb +14 -0
  28. data/lib/element_component/components/card/footer.rb +14 -0
  29. data/lib/element_component/components/card/header.rb +14 -0
  30. data/lib/element_component/components/card/image.rb +17 -0
  31. data/lib/element_component/components/card/text.rb +14 -0
  32. data/lib/element_component/components/card/title.rb +14 -0
  33. data/lib/element_component/components/card.rb +21 -0
  34. data/lib/element_component/components/carousel/caption.rb +14 -0
  35. data/lib/element_component/components/carousel/item.rb +16 -0
  36. data/lib/element_component/components/carousel.rb +70 -0
  37. data/lib/element_component/components/close_button.rb +17 -0
  38. data/lib/element_component/components/dropdown/divider.rb +20 -0
  39. data/lib/element_component/components/dropdown/header.rb +22 -0
  40. data/lib/element_component/components/dropdown/item.rb +33 -0
  41. data/lib/element_component/components/dropdown/menu.rb +15 -0
  42. data/lib/element_component/components/dropdown.rb +43 -0
  43. data/lib/element_component/components/list_group/item.rb +23 -0
  44. data/lib/element_component/components/list_group.rb +18 -0
  45. data/lib/element_component/components/modal/body.rb +14 -0
  46. data/lib/element_component/components/modal/content.rb +14 -0
  47. data/lib/element_component/components/modal/dialog.rb +21 -0
  48. data/lib/element_component/components/modal/footer.rb +14 -0
  49. data/lib/element_component/components/modal/header.rb +16 -0
  50. data/lib/element_component/components/modal/title.rb +14 -0
  51. data/lib/element_component/components/modal.rb +42 -0
  52. data/lib/element_component/components/nav/item.rb +14 -0
  53. data/lib/element_component/components/nav/link.rb +18 -0
  54. data/lib/element_component/components/nav.rb +23 -0
  55. data/lib/element_component/components/navbar/brand.rb +15 -0
  56. data/lib/element_component/components/navbar/collapse.rb +16 -0
  57. data/lib/element_component/components/navbar/nav.rb +14 -0
  58. data/lib/element_component/components/navbar/toggler.rb +22 -0
  59. data/lib/element_component/components/navbar.rb +51 -0
  60. data/lib/element_component/components/pagination/item.rb +27 -0
  61. data/lib/element_component/components/pagination.rb +33 -0
  62. data/lib/element_component/components/progress/bar.rb +24 -0
  63. data/lib/element_component/components/progress.rb +17 -0
  64. data/lib/element_component/components/spinner.rb +19 -0
  65. data/lib/element_component/components/table.rb +21 -0
  66. data/lib/element_component/components.rb +16 -0
  67. data/lib/element_component/version.rb +1 -1
  68. metadata +61 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8b23fff0ff2bf1424f46a3db32fb706973f1601f153dd68f9786018979eb1ef
4
- data.tar.gz: 631db1928c37d32d3a82190a0285e9fc85fb32693dc9fdba0fab9f118476fbcb
3
+ metadata.gz: 27a79b6578d299e674340e4c1e3c7a0061ff6ac3b9c233f102bb3d4d3fd89011
4
+ data.tar.gz: 501d6617270b0e5bc9cef324d270126d5cd531677cdd8ee43bb3a8e18bf1cd4c
5
5
  SHA512:
6
- metadata.gz: a1cf9210199d4eecaf263df1f99f8862541291cf0fa22fb9c6f92cf55cb1a68f60f72b154503dbce5889a5ffe3c42fd520ce915c7d3e08259d98008692b2d627
7
- data.tar.gz: 248a8b0ad689bc29c4c8b9c05b42479c6c613789f87d8992ae34cfbc5d06710955117782eb0db683073e8239b2dfc339c65ffbeb4f46e60414427423e87d8c84
6
+ metadata.gz: d7317e82f7fc54008e4f4152372fc1f3575edf3f0956790a87298025707bbcd507422c3a3e32800dd6aa6a3954f1006ac470fde4e44ffb35ba7aba1bfe9ed5e8
7
+ data.tar.gz: c358120b8d90a4ef0ea864094cae08d245286a20266c4c645814f7f60ec5795f5a62756a740e91be566b08159914674b91b0ba9af76013de02c0f3e20927d731
data/AGENTS.md CHANGED
@@ -44,13 +44,13 @@ 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 context |
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(context: :primary, dismissible: false, **attributes, &block)`
53
- - `context`: one of `:primary`, `:secondary`, `:success`, `:danger`, `:warning`, `:info`, `:light`, `:dark`
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
55
  - `&block`: instance_eval DSL for adding content inside the element
56
56
 
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 `instance_eval`-based blocks 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
 
@@ -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
- Components live under `ElementComponent::Components`.
149
+ ### Alert
117
150
 
118
- ### Alert (Bootstrap 5)
119
151
 
120
152
  ```ruby
121
- alert = ElementComponent::Components::Alert.new(context: :success) do
153
+ alert = ElementComponent::Components::Alert.new(variant: :success) do
122
154
  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
- **Contexts**: `:primary`, `:secondary`, `:success`, `:danger`, `:warning`, `:info`, `:light`, `:dark`
159
+ **Variants**: `:primary`, `:secondary`, `:success`, `:danger`, `:warning`, `:info`, `:light`, `:dark`
129
160
 
130
- #### Dismissible Alert
161
+ Dismissible alerts, headings, and links use sub-components:
131
162
 
132
163
  ```ruby
133
- alert = ElementComponent::Components::Alert.new(context: :warning, dismissible: true) do
134
- add_content("This can be closed.")
164
+ alert = ElementComponent::Components::Alert.new(variant: :warning, dismissible: true) do
165
+ add_content(ElementComponent::Components::AlertHeading.new { add_content("Warning") })
166
+ add_content("Please review. ")
167
+ add_content(ElementComponent::Components::AlertLink.new(href: "/details") { add_content("Details") })
135
168
  end
136
- puts alert.render
137
169
  # => <div class="alert alert-warning alert-dismissible" role="alert">
138
- # This can be closed.
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
- #### Alert with Heading and Link
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
- alert = ElementComponent::Components::Alert.new(context: :info) do
147
- add_content(ElementComponent::Components::AlertHeading.new.tap { |h| h.add_content("Notice") })
148
- add_content("Please review. ")
149
- add_content(ElementComponent::Components::AlertLink.new(href: "/details").tap { |l| l.add_content("Details") })
189
+ # Standard button
190
+ btn = ElementComponent::Components::Button.new(variant: :primary) { 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) { 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") { 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) { add_content("New") }
209
+ # => <span class="badge bg-primary">New</span>
210
+
211
+ pill = ElementComponent::Components::Badge.new(variant: :danger, pill: true) { 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
220
+ add_content(ElementComponent::Components::CardImage.new(src: "photo.jpg", top: true))
221
+ add_content(ElementComponent::Components::CardBody.new do
222
+ add_content(ElementComponent::Components::CardTitle.new { add_content("Title") })
223
+ add_content(ElementComponent::Components::CardText.new { add_content("Some text.") })
224
+ end)
225
+ add_content(ElementComponent::Components::CardFooter.new { 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
- #### Sub-components
229
+ **Sub-components**:
160
230
 
161
- | Class | Tag | Class |
231
+ | Class | Tag | CSS Class |
162
232
  |---|---|---|
163
- | `Alert` | `<div>` | `.alert .alert-{context}` |
164
- | `AlertHeading` | `<h4>` | `.alert-heading` |
165
- | `AlertLink` | `<a>` | `.alert-link` |
166
- | `AlertCloseButton` | `<button>` (self-closing) | `.btn-close` |
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
245
+ add_content(ElementComponent::Components::NavItem.new do
246
+ add_content(ElementComponent::Components::NavLink.new(href: "/", active: true) { add_content("Home") })
247
+ end)
248
+ add_content(ElementComponent::Components::NavItem.new do
249
+ add_content(ElementComponent::Components::NavLink.new(href: "/profile") { 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
262
+ add_content(ElementComponent::Components::BreadcrumbItem.new(href: "/") { add_content("Home") })
263
+ add_content(ElementComponent::Components::BreadcrumbItem.new(href: "/section") { add_content("Section") })
264
+ add_content(ElementComponent::Components::BreadcrumbItem.new(active: true) { 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
274
+ add_content(ElementComponent::Components::ListGroupItem.new { add_content("Item 1") })
275
+ add_content(ElementComponent::Components::ListGroupItem.new(active: true) { add_content("Item 2") })
276
+ add_content(ElementComponent::Components::ListGroupItem.new(href: "/link") { 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
288
+ add_content(ElementComponent::Components::ProgressBar.new(value: 75, variant: :success, striped: true) do
289
+ 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
311
+ add_content("<thead><tr><th>Name</th><th>Age</th></tr></thead>")
312
+ 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
324
+ add_content(ElementComponent::Components::PageItem.new(active: true) { add_content("1") })
325
+ add_content(ElementComponent::Components::PageItem.new { 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
335
+ add_content(ElementComponent::Components::Button.new(variant: :primary) { add_content("Left") })
336
+ add_content(ElementComponent::Components::Button.new(variant: :primary) { add_content("Middle") })
337
+ add_content(ElementComponent::Components::Button.new(variant: :primary) { 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
360
+ add_content(ElementComponent::Components::ModalContent.new do
361
+ add_content(ElementComponent::Components::ModalHeader.new do
362
+ add_content(ElementComponent::Components::ModalTitle.new { add_content("Modal title") })
363
+ end)
364
+ add_content(ElementComponent::Components::ModalBody.new { add_content("Modal body text.") })
365
+ add_content(ElementComponent::Components::ModalFooter.new do
366
+ add_content(ElementComponent::Components::Button.new(variant: :secondary) { add_content("Close") })
367
+ add_content(ElementComponent::Components::Button.new(variant: :primary) { 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
391
+ add_content(ElementComponent::Components::CarouselItem.new(active: true) do
392
+ add_content(%(<img src="slide1.jpg" class="d-block w-100" alt="...">))
393
+ end)
394
+ add_content(ElementComponent::Components::CarouselItem.new do
395
+ 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
414
+ 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") { add_content("Dropdown") }
420
+ )
421
+ add_content(
422
+ ElementComponent::Components::DropdownMenu.new do
423
+ add_content(ElementComponent::Components::DropdownItem.new { add_content("Action") })
424
+ add_content(ElementComponent::Components::DropdownItem.new(active: true) { add_content("Active") })
425
+ add_content(ElementComponent::Components::DropdownDivider.new)
426
+ add_content(ElementComponent::Components::DropdownItem.new(disabled: true) { 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
448
+ add_content(ElementComponent::Components::NavbarBrand.new(href: "/") { add_content("Brand") })
449
+ add_content(ElementComponent::Components::NavbarToggler.new(target: "nav"))
450
+ add_content(ElementComponent::Components::NavbarCollapse.new(id: "nav") do
451
+ add_content(ElementComponent::Components::NavbarNav.new do
452
+ add_content(ElementComponent::Components::NavItem.new do
453
+ add_content(ElementComponent::Components::NavLink.new(href: "/", active: true) { add_content("Home") })
454
+ end)
455
+ add_content(ElementComponent::Components::NavItem.new do
456
+ add_content(ElementComponent::Components::NavLink.new(href: "/about") { 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
- - [ ] Pre-built Bulma components
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
 
@@ -5,19 +5,19 @@ require_relative "../lib/element_component"
5
5
  # =============================================================================
6
6
  # Basic Alert
7
7
  # =============================================================================
8
- alert = ElementComponent::Components::Alert.new(context: :success)
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 Contexts
15
+ # All Variants
16
16
  # =============================================================================
17
17
  puts "=== All Alert Contexts ==="
18
- ElementComponent::Components::Alert::VALID_CONTEXTS.each do |context|
19
- alert = ElementComponent::Components::Alert.new(context: context)
20
- alert.add_content("#{context.capitalize} alert message here.")
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,7 +25,7 @@ puts
25
25
  # =============================================================================
26
26
  # Dismissible Alert (use block DSL so close button is always last)
27
27
  # =============================================================================
28
- alert = ElementComponent::Components::Alert.new(context: :warning, dismissible: true) do
28
+ alert = ElementComponent::Components::Alert.new(variant: :warning, dismissible: true) do
29
29
  add_content("This alert can be dismissed.")
30
30
  end
31
31
  puts "=== Dismissible Alert ==="
@@ -36,7 +36,7 @@ puts
36
36
  # Alert with Custom Attributes
37
37
  # =============================================================================
38
38
  alert = ElementComponent::Components::Alert.new(
39
- context: :primary,
39
+ variant: :primary,
40
40
  id: "main-alert",
41
41
  style: "margin-top: 20px;"
42
42
  )
@@ -48,7 +48,7 @@ puts
48
48
  # =============================================================================
49
49
  # Alert with Heading and Link (block DSL)
50
50
  # =============================================================================
51
- alert = ElementComponent::Components::Alert.new(context: :info) do
51
+ alert = ElementComponent::Components::Alert.new(variant: :info) do
52
52
  add_content(ElementComponent::Components::AlertHeading.new.tap { |h| h.add_content("Information") })
53
53
  add_content("This is an important notice. ")
54
54
  add_content(ElementComponent::Components::AlertLink.new(href: "/details").tap { |l| l.add_content("View details") })
@@ -60,7 +60,7 @@ puts
60
60
  # =============================================================================
61
61
  # Dismissible Alert with Heading and Link
62
62
  # =============================================================================
63
- alert = ElementComponent::Components::Alert.new(context: :danger, dismissible: true) do
63
+ alert = ElementComponent::Components::Alert.new(variant: :danger, dismissible: true) do
64
64
  add_content(ElementComponent::Components::AlertHeading.new.tap { |h| h.add_content("Error!") })
65
65
  add_content("Something went wrong. ")
66
66
  add_content(ElementComponent::Components::AlertLink.new(href: "/support").tap do |l|
@@ -74,7 +74,7 @@ puts
74
74
  # =============================================================================
75
75
  # Chained API Usage
76
76
  # =============================================================================
77
- alert = ElementComponent::Components::Alert.new(context: :secondary)
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
38
+ 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
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/element_component"
4
+
5
+ # =============================================================================
6
+ # Basic Breadcrumb
7
+ # =============================================================================
8
+ breadcrumb = ElementComponent::Components::Breadcrumb.new do
9
+ add_content(ElementComponent::Components::BreadcrumbItem.new(href: "/") { add_content("Home") })
10
+ add_content(ElementComponent::Components::BreadcrumbItem.new(href: "/library") { add_content("Library") })
11
+ add_content(ElementComponent::Components::BreadcrumbItem.new(active: true) { add_content("Data") })
12
+ end
13
+ puts "=== Basic Breadcrumb ==="
14
+ puts breadcrumb.render
15
+ puts
16
+
17
+ # =============================================================================
18
+ # Breadcrumb with Two Items
19
+ # =============================================================================
20
+ breadcrumb = ElementComponent::Components::Breadcrumb.new do
21
+ add_content(ElementComponent::Components::BreadcrumbItem.new(href: "/") { add_content("Home") })
22
+ add_content(ElementComponent::Components::BreadcrumbItem.new(active: true) { add_content("Current Page") })
23
+ end
24
+ puts "=== Two-Item Breadcrumb ==="
25
+ puts breadcrumb.render
26
+ puts
27
+
28
+ # =============================================================================
29
+ # Breadcrumb with Custom Class
30
+ # =============================================================================
31
+ breadcrumb = ElementComponent::Components::Breadcrumb.new(class: "custom-breadcrumb") do
32
+ add_content(ElementComponent::Components::BreadcrumbItem.new(href: "/") { add_content("Home") })
33
+ add_content(ElementComponent::Components::BreadcrumbItem.new(active: true) { add_content("Active") })
34
+ end
35
+ puts "=== Breadcrumb with Custom Class ==="
36
+ puts breadcrumb.render