element_component 0.5.0 → 0.6.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 +126 -0
- data/README.md +131 -23
- data/examples/alert_example.rb +97 -0
- data/lib/element_component/components/alert/close_button.rb +17 -0
- data/lib/element_component/components/alert/heading.rb +14 -0
- data/lib/element_component/components/alert/link.rb +15 -0
- data/lib/element_component/components/alert.rb +25 -0
- data/lib/element_component/components.rb +8 -0
- data/lib/element_component/element.rb +21 -7
- data/lib/element_component/version.rb +3 -1
- data/lib/element_component.rb +3 -1
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8b23fff0ff2bf1424f46a3db32fb706973f1601f153dd68f9786018979eb1ef
|
|
4
|
+
data.tar.gz: 631db1928c37d32d3a82190a0285e9fc85fb32693dc9fdba0fab9f118476fbcb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a1cf9210199d4eecaf263df1f99f8862541291cf0fa22fb9c6f92cf55cb1a68f60f72b154503dbce5889a5ffe3c42fd520ce915c7d3e08259d98008692b2d627
|
|
7
|
+
data.tar.gz: 248a8b0ad689bc29c4c8b9c05b42479c6c613789f87d8992ae34cfbc5d06710955117782eb0db683073e8239b2dfc339c65ffbeb4f46e60414427423e87d8c84
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# ElementComponent - Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
Ruby gem for building HTML programmatically with an object-oriented API. Provides dynamic attribute management, content nesting, and rendering hooks.
|
|
5
|
+
|
|
6
|
+
## Architecture
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
lib/
|
|
10
|
+
element_component.rb # Entry point, requires modules
|
|
11
|
+
element_component/
|
|
12
|
+
version.rb # VERSION constant
|
|
13
|
+
element.rb # Core Element class
|
|
14
|
+
components.rb # Component index, requires all components
|
|
15
|
+
components/
|
|
16
|
+
alert.rb # Alert component
|
|
17
|
+
alert/
|
|
18
|
+
heading.rb # AlertHeading component
|
|
19
|
+
link.rb # AlertLink component
|
|
20
|
+
close_button.rb # AlertCloseButton component
|
|
21
|
+
spec/
|
|
22
|
+
element_component_spec.rb # Version check
|
|
23
|
+
lib/
|
|
24
|
+
element_spec.rb # Element unit tests
|
|
25
|
+
components/
|
|
26
|
+
alert_spec.rb # Alert component tests
|
|
27
|
+
spec_helper.rb # RSpec config
|
|
28
|
+
examples/
|
|
29
|
+
alert_example.rb # Complete Alert usage examples
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Core Classes
|
|
33
|
+
|
|
34
|
+
### `ElementComponent::Element`
|
|
35
|
+
- **Attributes**: `element` (tag name), `attributes` (Hash), `contents` (Array), `html` (rendered output)
|
|
36
|
+
- **Constructor**: `Element.new(tag_name, closing_tag: true, **attributes)`
|
|
37
|
+
- **Content methods**: `add_content`, `add_content!`, `add_content(&block)`
|
|
38
|
+
- **Attribute methods**: `add_attribute`, `add_attribute!`, `remove_attribute`, `remove_attribute_value`
|
|
39
|
+
- **Render**: `render` (with hooks: `before_render`, `after_render`, `around_render`)
|
|
40
|
+
|
|
41
|
+
### Pre-built Components
|
|
42
|
+
|
|
43
|
+
Components live under `ElementComponent::Components`. Each component folder contains the main class and its sub-components in separate files.
|
|
44
|
+
|
|
45
|
+
| Component | Class | Description |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| Alert | `Components::Alert` | `.alert` container with context |
|
|
48
|
+
| Alert Heading | `Components::AlertHeading` | `.alert-heading` (`<h4>`) |
|
|
49
|
+
| Alert Link | `Components::AlertLink` | `.alert-link` (`<a>`) |
|
|
50
|
+
| Alert Close | `Components::AlertCloseButton` | `.btn-close` (self-closing `<button>`) |
|
|
51
|
+
|
|
52
|
+
**Alert constructor**: `Alert.new(context: :primary, dismissible: false, **attributes, &block)`
|
|
53
|
+
- `context`: one of `:primary`, `:secondary`, `:success`, `:danger`, `:warning`, `:info`, `:light`, `:dark`
|
|
54
|
+
- `dismissible`: adds `.alert-dismissible` class and appends a `CloseButton`
|
|
55
|
+
- `&block`: instance_eval DSL for adding content inside the element
|
|
56
|
+
|
|
57
|
+
**Component guidelines**:
|
|
58
|
+
- Always use `add_attribute()` instead of manipulating the `attributes` hash directly
|
|
59
|
+
- Each class in its own file under a component-named folder
|
|
60
|
+
- Call `super("tag_name", closing_tag: ..., &block)` first, then chain `add_attribute` calls
|
|
61
|
+
- `instance_eval(&block)` lives in `Element#initialize` — do NOT repeat it in components
|
|
62
|
+
- Pass user attributes last via `add_attribute(attributes)`
|
|
63
|
+
|
|
64
|
+
### Sub-component example pattern:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
class AlertHeading < Element
|
|
68
|
+
def initialize(**attributes, &block)
|
|
69
|
+
super("h4", &block)
|
|
70
|
+
add_attribute(class: "alert-heading")
|
|
71
|
+
add_attribute(attributes)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Coding Conventions
|
|
77
|
+
|
|
78
|
+
- **Language**: Ruby 3.1+
|
|
79
|
+
- **Style**: Double quotes for strings
|
|
80
|
+
- **Testing**: RSpec with `expect` syntax, `subject` pattern
|
|
81
|
+
- **Linting**: RuboCop (run `bundle exec rubocop`)
|
|
82
|
+
- **Pattern matching**: Use `in` pattern matching (`case content; in Element; ...`)
|
|
83
|
+
|
|
84
|
+
## Common Commands
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
bin/setup # Install dependencies
|
|
88
|
+
bundle exec rspec # Run tests
|
|
89
|
+
bundle exec rubocop # Lint
|
|
90
|
+
bundle exec rake spec # Run tests via rake
|
|
91
|
+
bundle exec rake rubocop # Lint via rake
|
|
92
|
+
bundle exec rake # Default: spec + rubocop
|
|
93
|
+
bin/console # Interactive console
|
|
94
|
+
ruby examples/alert_example.rb # Run Alert examples
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Testing Patterns
|
|
98
|
+
|
|
99
|
+
- Use `subject` for the element under test
|
|
100
|
+
- Use `before` blocks to set up state
|
|
101
|
+
- Test render output as raw HTML strings
|
|
102
|
+
- Test attribute hashes directly
|
|
103
|
+
- Follow existing describe/context/it structure
|
|
104
|
+
|
|
105
|
+
## Development Workflow
|
|
106
|
+
|
|
107
|
+
1. Write/update specs first
|
|
108
|
+
2. Implement in `lib/element_component/element.rb` or `lib/element_component/components/`
|
|
109
|
+
3. Run `bundle exec rspec` to verify
|
|
110
|
+
4. Run `bundle exec rubocop` for linting
|
|
111
|
+
5. Run `bundle exec rake` for full check
|
|
112
|
+
|
|
113
|
+
## Key Design Decisions
|
|
114
|
+
|
|
115
|
+
- Attributes stored as Hash of Symbol => Array of values (supports multiple values per attribute)
|
|
116
|
+
- Contents stored as Array (supports strings, Elements, and Procs/blocks)
|
|
117
|
+
- `!` suffix methods reset state before adding (e.g., `add_content!` clears then adds)
|
|
118
|
+
- Hooks (`before_render`, `after_render`, `around_render`) are optional, detected via `respond_to?`
|
|
119
|
+
- Self-closing tags controlled by `closing_tag:` parameter
|
|
120
|
+
- Component classes use `add_attribute()` instead of direct hash manipulation
|
|
121
|
+
|
|
122
|
+
## Roadmap Features (from README)
|
|
123
|
+
- Caching support
|
|
124
|
+
- Pre-built Bulma components
|
|
125
|
+
- Pre-built Bootstrap components
|
|
126
|
+
- Enhanced DSL for nested structures
|
data/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
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,
|
|
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.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Add to your application's Gemfile:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
bundle add element_component
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Or install directly:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
gem install element_component
|
|
@@ -18,9 +18,7 @@ gem install element_component
|
|
|
18
18
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
|
-
###
|
|
22
|
-
|
|
23
|
-
Create a simple element and render it to HTML:
|
|
21
|
+
### Creating Elements
|
|
24
22
|
|
|
25
23
|
```ruby
|
|
26
24
|
p = ElementComponent::Element.new("p", class: "text-bold")
|
|
@@ -29,30 +27,58 @@ puts p.render
|
|
|
29
27
|
# => <p class="text-bold">Hello, World!</p>
|
|
30
28
|
```
|
|
31
29
|
|
|
32
|
-
###
|
|
30
|
+
### Block DSL
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
Use a block to add content inline:
|
|
35
33
|
|
|
36
34
|
```ruby
|
|
37
|
-
div = ElementComponent::Element.new("div", class: "container")
|
|
38
|
-
|
|
39
|
-
h1
|
|
35
|
+
div = ElementComponent::Element.new("div", class: "container") do
|
|
36
|
+
add_content("Welcome")
|
|
37
|
+
add_content(ElementComponent::Element.new("h1") { add_content("Title") })
|
|
38
|
+
end
|
|
39
|
+
puts div.render
|
|
40
|
+
# => <div class="container">Welcome<h1>Title</h1></div>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### The `new_element` Helper
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
div.add_content("This is a simple HTML builder.")
|
|
45
|
+
Inside a block, use `new_element` as a shorthand:
|
|
43
46
|
|
|
47
|
+
```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") })
|
|
51
|
+
end
|
|
44
52
|
puts div.render
|
|
45
|
-
# => <div class="
|
|
53
|
+
# => <div><h1>Hello</h1><p class="lead">World</p></div>
|
|
46
54
|
```
|
|
47
55
|
|
|
48
|
-
###
|
|
56
|
+
### Content Types
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
Content can be a string, an Element, or a block (Proc):
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
div = ElementComponent::Element.new("div")
|
|
62
|
+
|
|
63
|
+
# String
|
|
64
|
+
div.add_content("plain text")
|
|
65
|
+
|
|
66
|
+
# Element instance
|
|
67
|
+
div.add_content(ElementComponent::Element.new("span") { add_content("nested") })
|
|
68
|
+
|
|
69
|
+
# Block (evaluated at render time, has access to new_element)
|
|
70
|
+
div.add_content { new_element("em") { add_content("deferred") } }
|
|
71
|
+
|
|
72
|
+
puts div.render
|
|
73
|
+
# => <div>plain text<span>nested</span><em>deferred</em></div>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Attribute Management
|
|
51
77
|
|
|
52
78
|
```ruby
|
|
53
79
|
btn = ElementComponent::Element.new("button", class: "btn", type: "button")
|
|
54
80
|
|
|
55
|
-
# Add more values to an attribute
|
|
81
|
+
# Add more values to an attribute
|
|
56
82
|
btn.add_attribute(class: "btn-primary")
|
|
57
83
|
|
|
58
84
|
# Reset attributes and set new ones
|
|
@@ -67,8 +93,6 @@ btn.remove_attribute_value(:class, "btn-primary")
|
|
|
67
93
|
|
|
68
94
|
### Self-Closing Tags
|
|
69
95
|
|
|
70
|
-
You can specify if an element should have a closing tag:
|
|
71
|
-
|
|
72
96
|
```ruby
|
|
73
97
|
img = ElementComponent::Element.new("img", closing_tag: false, src: "image.png", alt: "Logo")
|
|
74
98
|
puts img.render
|
|
@@ -77,19 +101,103 @@ puts img.render
|
|
|
77
101
|
|
|
78
102
|
### Rendering Hooks
|
|
79
103
|
|
|
80
|
-
`ElementComponent` supports `before_render`, `after_render`, and `around_render` hooks
|
|
104
|
+
`ElementComponent` supports `before_render`, `after_render`, and `around_render` hooks:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
div = ElementComponent::Element.new("div")
|
|
108
|
+
div.define_singleton_method(:before_render) { add_attribute(class: "dynamic") }
|
|
109
|
+
div.add_content("content")
|
|
110
|
+
puts div.render
|
|
111
|
+
# => <div class="dynamic">content</div>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Pre-built Components
|
|
115
|
+
|
|
116
|
+
Components live under `ElementComponent::Components`.
|
|
117
|
+
|
|
118
|
+
### Alert (Bootstrap 5)
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
alert = ElementComponent::Components::Alert.new(context: :success) do
|
|
122
|
+
add_content("Operation completed!")
|
|
123
|
+
end
|
|
124
|
+
puts alert.render
|
|
125
|
+
# => <div class="alert alert-success" role="alert">Operation completed!</div>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Contexts**: `:primary`, `:secondary`, `:success`, `:danger`, `:warning`, `:info`, `:light`, `:dark`
|
|
129
|
+
|
|
130
|
+
#### Dismissible Alert
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
alert = ElementComponent::Components::Alert.new(context: :warning, dismissible: true) do
|
|
134
|
+
add_content("This can be closed.")
|
|
135
|
+
end
|
|
136
|
+
puts alert.render
|
|
137
|
+
# => <div class="alert alert-warning alert-dismissible" role="alert">
|
|
138
|
+
# This can be closed.
|
|
139
|
+
# <button class="btn-close" data-bs-dismiss="alert" aria-label="Close">
|
|
140
|
+
# </div>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Alert with Heading and Link
|
|
144
|
+
|
|
145
|
+
```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") })
|
|
150
|
+
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
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Sub-components
|
|
160
|
+
|
|
161
|
+
| Class | Tag | Class |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| `Alert` | `<div>` | `.alert .alert-{context}` |
|
|
164
|
+
| `AlertHeading` | `<h4>` | `.alert-heading` |
|
|
165
|
+
| `AlertLink` | `<a>` | `.alert-link` |
|
|
166
|
+
| `AlertCloseButton` | `<button>` (self-closing) | `.btn-close` |
|
|
81
167
|
|
|
82
168
|
## Development
|
|
83
169
|
|
|
84
|
-
|
|
170
|
+
```bash
|
|
171
|
+
bin/setup # Install dependencies
|
|
172
|
+
bundle exec rspec # Run tests
|
|
173
|
+
bundle exec rubocop # Lint
|
|
174
|
+
bundle exec rake # Spec + RuboCop
|
|
175
|
+
bin/console # Interactive console
|
|
176
|
+
ruby examples/alert_example.rb # Run Alert examples
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Test Coverage
|
|
180
|
+
|
|
181
|
+
Run with coverage reporting:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
COVERAGE=true bundle exec rspec
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Release
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Update version in lib/element_component/version.rb
|
|
191
|
+
bundle exec rake release
|
|
192
|
+
```
|
|
85
193
|
|
|
86
|
-
|
|
194
|
+
Or push a version tag (e.g., `v0.6.0`) to trigger the automated release workflow.
|
|
87
195
|
|
|
88
196
|
## Roadmap
|
|
89
197
|
|
|
90
198
|
- [ ] Support for Caching
|
|
91
199
|
- [ ] Pre-built Bulma components
|
|
92
|
-
- [ ] Pre-built Bootstrap components
|
|
200
|
+
- [ ] Pre-built Bootstrap components (more)
|
|
93
201
|
- [ ] Enhanced DSL for nested structures
|
|
94
202
|
|
|
95
203
|
## Contributing
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/element_component"
|
|
4
|
+
|
|
5
|
+
# =============================================================================
|
|
6
|
+
# Basic Alert
|
|
7
|
+
# =============================================================================
|
|
8
|
+
alert = ElementComponent::Components::Alert.new(context: :success)
|
|
9
|
+
alert.add_content("Operation completed successfully!")
|
|
10
|
+
puts "=== Basic Alert ==="
|
|
11
|
+
puts alert.render
|
|
12
|
+
puts
|
|
13
|
+
|
|
14
|
+
# =============================================================================
|
|
15
|
+
# All Contexts
|
|
16
|
+
# =============================================================================
|
|
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.")
|
|
21
|
+
puts alert.render
|
|
22
|
+
end
|
|
23
|
+
puts
|
|
24
|
+
|
|
25
|
+
# =============================================================================
|
|
26
|
+
# Dismissible Alert (use block DSL so close button is always last)
|
|
27
|
+
# =============================================================================
|
|
28
|
+
alert = ElementComponent::Components::Alert.new(context: :warning, dismissible: true) do
|
|
29
|
+
add_content("This alert can be dismissed.")
|
|
30
|
+
end
|
|
31
|
+
puts "=== Dismissible Alert ==="
|
|
32
|
+
puts alert.render
|
|
33
|
+
puts
|
|
34
|
+
|
|
35
|
+
# =============================================================================
|
|
36
|
+
# Alert with Custom Attributes
|
|
37
|
+
# =============================================================================
|
|
38
|
+
alert = ElementComponent::Components::Alert.new(
|
|
39
|
+
context: :primary,
|
|
40
|
+
id: "main-alert",
|
|
41
|
+
style: "margin-top: 20px;"
|
|
42
|
+
)
|
|
43
|
+
alert.add_content("This alert has a custom ID and inline style.")
|
|
44
|
+
puts "=== Alert with Custom Attributes ==="
|
|
45
|
+
puts alert.render
|
|
46
|
+
puts
|
|
47
|
+
|
|
48
|
+
# =============================================================================
|
|
49
|
+
# Alert with Heading and Link (block DSL)
|
|
50
|
+
# =============================================================================
|
|
51
|
+
alert = ElementComponent::Components::Alert.new(context: :info) do
|
|
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") })
|
|
55
|
+
end
|
|
56
|
+
puts "=== Alert with Heading and Link ==="
|
|
57
|
+
puts alert.render
|
|
58
|
+
puts
|
|
59
|
+
|
|
60
|
+
# =============================================================================
|
|
61
|
+
# Dismissible Alert with Heading and Link
|
|
62
|
+
# =============================================================================
|
|
63
|
+
alert = ElementComponent::Components::Alert.new(context: :danger, dismissible: true) do
|
|
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|
|
|
67
|
+
l.add_content("Contact support")
|
|
68
|
+
end)
|
|
69
|
+
end
|
|
70
|
+
puts "=== Dismissible Alert with Heading and Link ==="
|
|
71
|
+
puts alert.render
|
|
72
|
+
puts
|
|
73
|
+
|
|
74
|
+
# =============================================================================
|
|
75
|
+
# Chained API Usage
|
|
76
|
+
# =============================================================================
|
|
77
|
+
alert = ElementComponent::Components::Alert.new(context: :secondary)
|
|
78
|
+
.add_content("This alert was built using chained calls.")
|
|
79
|
+
puts "=== Chained API ==="
|
|
80
|
+
puts alert.render
|
|
81
|
+
puts
|
|
82
|
+
|
|
83
|
+
# =============================================================================
|
|
84
|
+
# Using Sub-Components Independently
|
|
85
|
+
# =============================================================================
|
|
86
|
+
heading = ElementComponent::Components::AlertHeading.new
|
|
87
|
+
heading.add_content("Standalone Heading")
|
|
88
|
+
|
|
89
|
+
link = ElementComponent::Components::AlertLink.new(href: "https://example.com")
|
|
90
|
+
link.add_content("Standalone Link")
|
|
91
|
+
|
|
92
|
+
close_btn = ElementComponent::Components::AlertCloseButton.new
|
|
93
|
+
|
|
94
|
+
puts "=== Sub-Components Rendered Independently ==="
|
|
95
|
+
puts heading.render
|
|
96
|
+
puts link.render
|
|
97
|
+
puts close_btn.render
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ElementComponent
|
|
4
|
+
module Components
|
|
5
|
+
class AlertCloseButton < Element
|
|
6
|
+
def initialize(**attributes)
|
|
7
|
+
super("button", closing_tag: false)
|
|
8
|
+
|
|
9
|
+
add_attribute(class: "btn-close")
|
|
10
|
+
add_attribute(type: "button")
|
|
11
|
+
add_attribute("data-bs-dismiss": "alert")
|
|
12
|
+
add_attribute("aria-label": "Close")
|
|
13
|
+
add_attribute(attributes) unless attributes.empty?
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ElementComponent
|
|
4
|
+
module Components
|
|
5
|
+
class AlertHeading < Element
|
|
6
|
+
def initialize(**attributes)
|
|
7
|
+
super("h4")
|
|
8
|
+
|
|
9
|
+
add_attribute(class: "alert-heading")
|
|
10
|
+
add_attribute(attributes) unless attributes.empty?
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ElementComponent
|
|
4
|
+
module Components
|
|
5
|
+
class AlertLink < Element
|
|
6
|
+
def initialize(href: "#", **attributes)
|
|
7
|
+
super("a")
|
|
8
|
+
|
|
9
|
+
add_attribute(class: "alert-link")
|
|
10
|
+
add_attribute(href: href)
|
|
11
|
+
add_attribute(attributes) unless attributes.empty?
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "alert/heading"
|
|
4
|
+
require_relative "alert/link"
|
|
5
|
+
require_relative "alert/close_button"
|
|
6
|
+
|
|
7
|
+
module ElementComponent
|
|
8
|
+
module Components
|
|
9
|
+
class Alert < Element
|
|
10
|
+
VALID_CONTEXTS = %i[primary secondary success danger warning info light dark].freeze
|
|
11
|
+
|
|
12
|
+
def initialize(context: :primary, dismissible: false, **attributes, &block)
|
|
13
|
+
super("div", &block)
|
|
14
|
+
|
|
15
|
+
add_attribute(class: "alert")
|
|
16
|
+
add_attribute(class: "alert-#{context}")
|
|
17
|
+
add_attribute(class: "alert-dismissible") if dismissible
|
|
18
|
+
add_attribute(role: "alert")
|
|
19
|
+
|
|
20
|
+
add_attribute(attributes) unless attributes.empty?
|
|
21
|
+
add_content(AlertCloseButton.new) if dismissible
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ElementComponent
|
|
2
4
|
class Element
|
|
3
5
|
attr_reader :element, :attributes, :contents, :html
|
|
4
6
|
|
|
5
|
-
def initialize(element, closing_tag: true, **attribute)
|
|
7
|
+
def initialize(element, closing_tag: true, **attribute, &block)
|
|
6
8
|
@element = element
|
|
7
9
|
@closing_tag = closing_tag
|
|
8
|
-
@html =
|
|
10
|
+
@html = String.new
|
|
9
11
|
|
|
10
12
|
add_attribute!(attribute)
|
|
11
13
|
reset_contents!
|
|
14
|
+
instance_eval(&block) if block
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
def add_content!(content)
|
|
@@ -64,18 +67,20 @@ module ElementComponent
|
|
|
64
67
|
@attributes = {}
|
|
65
68
|
end
|
|
66
69
|
|
|
67
|
-
def new_element(
|
|
70
|
+
def new_element(...) = Element.new(...)
|
|
68
71
|
|
|
69
72
|
def render
|
|
70
|
-
|
|
73
|
+
@html = String.new
|
|
74
|
+
|
|
75
|
+
before_render if respond_to? "before_render"
|
|
71
76
|
|
|
72
|
-
if respond_to?
|
|
77
|
+
if respond_to? "around_render"
|
|
73
78
|
around_render { build }
|
|
74
79
|
else
|
|
75
80
|
build
|
|
76
81
|
end
|
|
77
82
|
|
|
78
|
-
after_render(@html) if respond_to?
|
|
83
|
+
after_render(@html) if respond_to? "after_render"
|
|
79
84
|
|
|
80
85
|
@html
|
|
81
86
|
end
|
|
@@ -95,10 +100,19 @@ module ElementComponent
|
|
|
95
100
|
end
|
|
96
101
|
|
|
97
102
|
def mount_content
|
|
98
|
-
@contents.map do |content|
|
|
103
|
+
@contents.dup.map do |content|
|
|
99
104
|
case content
|
|
100
105
|
in Element
|
|
101
106
|
content.render
|
|
107
|
+
in Proc
|
|
108
|
+
result = instance_eval(&content)
|
|
109
|
+
if result.equal?(self)
|
|
110
|
+
""
|
|
111
|
+
elsif result.respond_to?(:render)
|
|
112
|
+
result.render
|
|
113
|
+
else
|
|
114
|
+
result.to_s
|
|
115
|
+
end
|
|
102
116
|
else
|
|
103
117
|
content.to_s
|
|
104
118
|
end
|
data/lib/element_component.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative "element_component/version"
|
|
2
4
|
require_relative "element_component/element"
|
|
5
|
+
require_relative "element_component/components"
|
|
3
6
|
|
|
4
7
|
module ElementComponent
|
|
5
8
|
class Error < StandardError; end
|
|
6
|
-
# Your code goes here...
|
|
7
9
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: element_component
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- João Paulo Correia
|
|
@@ -18,10 +18,17 @@ extra_rdoc_files: []
|
|
|
18
18
|
files:
|
|
19
19
|
- ".rspec"
|
|
20
20
|
- ".rubocop.yml"
|
|
21
|
+
- AGENTS.md
|
|
21
22
|
- LICENSE.txt
|
|
22
23
|
- README.md
|
|
23
24
|
- Rakefile
|
|
25
|
+
- examples/alert_example.rb
|
|
24
26
|
- lib/element_component.rb
|
|
27
|
+
- lib/element_component/components.rb
|
|
28
|
+
- lib/element_component/components/alert.rb
|
|
29
|
+
- lib/element_component/components/alert/close_button.rb
|
|
30
|
+
- lib/element_component/components/alert/heading.rb
|
|
31
|
+
- lib/element_component/components/alert/link.rb
|
|
25
32
|
- lib/element_component/element.rb
|
|
26
33
|
- lib/element_component/version.rb
|
|
27
34
|
homepage: https://github.com/joaopaulocorreia/element_component
|