cubit_components 0.1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +58 -0
- data/Rakefile +8 -0
- data/app/assets/stylesheets/cubit_components/application.css +6 -0
- data/app/assets/tailwind/cubit_components/engine.css +4 -0
- data/config/routes.rb +2 -0
- data/lib/cubit_components/base_component.rb +39 -0
- data/lib/cubit_components/bleed_component.rb +26 -0
- data/lib/cubit_components/box_component.rb +8 -0
- data/lib/cubit_components/columns_component.rb +48 -0
- data/lib/cubit_components/configuration.rb +18 -0
- data/lib/cubit_components/content_block_component.rb +21 -0
- data/lib/cubit_components/engine.rb +14 -0
- data/lib/cubit_components/inline_component.rb +19 -0
- data/lib/cubit_components/page_block_component.rb +37 -0
- data/lib/cubit_components/page_component.rb +34 -0
- data/lib/cubit_components/previews/bleed_component_preview.rb +13 -0
- data/lib/cubit_components/previews/box_component_preview.rb +12 -0
- data/lib/cubit_components/previews/columns_component_preview.rb +20 -0
- data/lib/cubit_components/previews/components_index.rb +28 -0
- data/lib/cubit_components/previews/content_block_component_preview.rb +13 -0
- data/lib/cubit_components/previews/inline_component_preview.rb +25 -0
- data/lib/cubit_components/previews/page_block_component_preview.rb +13 -0
- data/lib/cubit_components/previews/page_component_preview.rb +17 -0
- data/lib/cubit_components/previews/preview_helper.rb +15 -0
- data/lib/cubit_components/previews/spread_component_preview.rb +46 -0
- data/lib/cubit_components/previews/stack_component_preview.rb +22 -0
- data/lib/cubit_components/previews/tiles_component_preview.rb +20 -0
- data/lib/cubit_components/spread_component.rb +59 -0
- data/lib/cubit_components/stack_component.rb +32 -0
- data/lib/cubit_components/tailwind_generated_classes_chat.txt +127 -0
- data/lib/cubit_components/tiles_component.rb +43 -0
- data/lib/cubit_components/vattr_validator.rb +53 -0
- data/lib/cubit_components/version.rb +3 -0
- data/lib/cubit_components.rb +34 -0
- data/lib/generators/cubit/install/install_generator.rb +63 -0
- data/lib/tasks/cubit_components_tasks.rake +4 -0
- metadata +208 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cb5dec0910e548ed2c02c397e6d86114e03ce01bec318de5226908b947a5d83b
|
4
|
+
data.tar.gz: 197294b2c0f689acd25e06bf41bbc4ca932336992768b45e594ff43d54043418
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 29a419c390f2df73fd6e6783370e68aac8b028620ec9073d49d5f17bc6051a98f0ef49b676dcc1bc3019729b8f6bf4668c257d453d71ce5819544455148a6e00
|
7
|
+
data.tar.gz: 1f9502b054915d5071ee4f660d6cab32184ce1761e79eab82f5f0884f62a312da2771dd1af4e111a14100d36e1a78a7591f4b1a80207a37ad6fe6c863bfb8983
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright Fabio Papa
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# CubitComponents
|
2
|
+
|
3
|
+
A collection of reusable ViewComponents with standardized styling and utility class validation.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add to your Gemfile:
|
8
|
+
```ruby
|
9
|
+
gem 'cubit_components'
|
10
|
+
```
|
11
|
+
Then run:
|
12
|
+
```bash
|
13
|
+
bundle install
|
14
|
+
```
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
### Basic Component
|
19
|
+
```erb
|
20
|
+
<%= render CubitComponents::BoxComponent.new do %>
|
21
|
+
Box content
|
22
|
+
<% end %>
|
23
|
+
```
|
24
|
+
|
25
|
+
### With Custom Classes
|
26
|
+
```erb
|
27
|
+
<%= render CubitComponents::StackComponent.new(classes: "bg-gray-100") do %>
|
28
|
+
<div>Item 1</div>
|
29
|
+
<div>Item 2</div>
|
30
|
+
<% end %>
|
31
|
+
```
|
32
|
+
|
33
|
+
### Available Components
|
34
|
+
- BoxComponent - Basic container
|
35
|
+
- StackComponent - Vertical layout
|
36
|
+
- ColumnsComponent - Horizontal layout
|
37
|
+
- BleedComponent - Negative margins
|
38
|
+
- ContentBlockComponent - Width-constrained container
|
39
|
+
|
40
|
+
## Configuration
|
41
|
+
|
42
|
+
Create an initializer to customize validation behavior:
|
43
|
+
```ruby
|
44
|
+
# config/initializers/cubit_components.rb
|
45
|
+
CubitComponents.configure do |config|
|
46
|
+
config.invalid_vattr_behavior = :raise # :warn, :silent
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
## Development
|
51
|
+
After checking out the repo:
|
52
|
+
```bash
|
53
|
+
bin/setup
|
54
|
+
bundle exec rspec
|
55
|
+
```
|
56
|
+
|
57
|
+
## License
|
58
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/config/routes.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "view_component"
|
4
|
+
require_relative "vattr_validator"
|
5
|
+
|
6
|
+
module CubitComponents
|
7
|
+
class BaseComponent < ViewComponent::Base
|
8
|
+
include ::CubitComponents::VattrValidator
|
9
|
+
|
10
|
+
# Valid “utility” / “variant” classes
|
11
|
+
VALID_VATTRS = Set.new(
|
12
|
+
%w[gap-small gap-medium text-left text-center text-right]
|
13
|
+
).freeze
|
14
|
+
|
15
|
+
# @param vattrs [Array<String>] extra utility / variant classes
|
16
|
+
def initialize(element: "div", classes: "", vattrs: [])
|
17
|
+
@element = element
|
18
|
+
@classes = classes
|
19
|
+
@vattrs = vattrs
|
20
|
+
|
21
|
+
validate_vattrs(vattrs, self.class::VALID_VATTRS) if validate_vattrs?
|
22
|
+
end
|
23
|
+
|
24
|
+
def call
|
25
|
+
content_tag @element, content, class: all_classes
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def all_classes
|
31
|
+
(Array(@vattrs) + Array(@classes.to_s.split)).join(" ").strip
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sub-classes may disable validation
|
35
|
+
def validate_vattrs?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CubitComponents
|
2
|
+
class BleedComponent < BaseComponent
|
3
|
+
# Allow negative margin utility classes.
|
4
|
+
# They follow Tailwind's pattern, e.g., "-m-3" or "sm:-mx-4"
|
5
|
+
VALID_VATTRS = Set.new(
|
6
|
+
begin
|
7
|
+
breakpoints = ["", "sm:", "md:", "lg:", "xl:"]
|
8
|
+
directions = ["", "x", "y", "t", "r", "b", "l"]
|
9
|
+
values = %w[1 2 3 4 5]
|
10
|
+
breakpoints.flat_map do |bp|
|
11
|
+
directions.flat_map do |dir|
|
12
|
+
values.map do |val|
|
13
|
+
# For no breakpoint, produces e.g. "-m-3"
|
14
|
+
# For a breakpoint, produces e.g. "sm:-mx-4"
|
15
|
+
"#{bp}-m#{dir}-#{val}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
).freeze
|
21
|
+
|
22
|
+
def initialize(element: "div", classes: "", vattrs: [])
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CubitComponents
|
4
|
+
# Horizontal flex-layout that can collapse into a vertical stack.
|
5
|
+
class ColumnsComponent < BaseComponent
|
6
|
+
VALID_VATTRS = Set.new(
|
7
|
+
%w[
|
8
|
+
gap-small gap-medium gap-large
|
9
|
+
items-start items-center items-end
|
10
|
+
text-left text-center text-right
|
11
|
+
]
|
12
|
+
).freeze
|
13
|
+
|
14
|
+
# :reverse – Boolean, applies `flex-row-reverse`
|
15
|
+
# :collapse_below – breakpoint string, ex. "sm" → "sm:flex-col"
|
16
|
+
# `reverse` can only be true when `collapse_below` is non-nil
|
17
|
+
def initialize(element: "div", classes: "", vattrs: [], reverse: false, collapse_below: nil)
|
18
|
+
@reverse = reverse
|
19
|
+
@collapse_below = collapse_below
|
20
|
+
|
21
|
+
super(
|
22
|
+
element: element,
|
23
|
+
classes: build_layout_classes(classes),
|
24
|
+
vattrs: vattrs
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Nested column – accepts any utility classes, no validation
|
29
|
+
class ColumnComponent < BaseComponent
|
30
|
+
private def validate_vattrs? = false
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def build_layout_classes(user)
|
36
|
+
base = %w[flex]
|
37
|
+
base << if @collapse_below && @reverse
|
38
|
+
"flex-col-reverse"
|
39
|
+
elsif @collapse_below
|
40
|
+
"flex-col"
|
41
|
+
else
|
42
|
+
"flex-row"
|
43
|
+
end
|
44
|
+
base << "#{@collapse_below}:flex-row" if @collapse_below
|
45
|
+
(base + user.to_s.split).join(" ")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CubitComponents
|
2
|
+
class Configuration
|
3
|
+
attr_reader :invalid_vattr_behavior
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
# Set default values
|
7
|
+
@invalid_vattr_behavior = :warn
|
8
|
+
end
|
9
|
+
|
10
|
+
def invalid_vattr_behavior=(value)
|
11
|
+
unless [:warn, :raise, :silent].include?(value)
|
12
|
+
raise ArgumentError, "invalid_class_behavior must be :warn, :raise, or :silent"
|
13
|
+
end
|
14
|
+
|
15
|
+
@invalid_vattr_behavior = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module CubitComponents
|
2
|
+
class ContentBlockComponent < BaseComponent
|
3
|
+
DEFAULT_CLASSES = "max-w-screen mx-auto"
|
4
|
+
VALID_VATTRS = Set.new(
|
5
|
+
%w[
|
6
|
+
max-w-3xs max-w-2xs max-w-xs
|
7
|
+
max-w-sm max-w-md max-w-lg
|
8
|
+
max-w-xl max-w-2xl max-w-3xl max-w-4xl max-w-5xl max-w-6xl max-w-7xl
|
9
|
+
max-w-none max-w-full max-w-screen
|
10
|
+
]
|
11
|
+
)
|
12
|
+
|
13
|
+
# No extra vattr validation is required, so simply merge with default classes.
|
14
|
+
def initialize(element: "div", classes: "", vattrs: [])
|
15
|
+
default_classes = DEFAULT_CLASSES.split(" ")
|
16
|
+
default_classes.delete("max-w-screen") if vattrs.any? { |v| v.start_with?("max-w-") }
|
17
|
+
merged_classes = [*default_classes, classes].join(" ").strip
|
18
|
+
super(element: element, classes: merged_classes, vattrs: vattrs)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "rails"
|
2
|
+
require "view_component"
|
3
|
+
|
4
|
+
module CubitComponents
|
5
|
+
class Engine < ::Rails::Engine
|
6
|
+
config.view_component.preview_paths << root.join("lib/cubit_components/previews")
|
7
|
+
|
8
|
+
isolate_namespace CubitComponents
|
9
|
+
|
10
|
+
initializer "cubit_components.assets" do |app|
|
11
|
+
app.config.assets.precompile += %w[cubit_components/application.css]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CubitComponents
|
4
|
+
class InlineComponent < BaseComponent
|
5
|
+
VALID_VATTRS = Set.new(
|
6
|
+
%w[gap-6 gap-small gap-medium gap-large text-left text-center text-right]
|
7
|
+
).freeze
|
8
|
+
|
9
|
+
# collapse_below – e.g. "sm", "md"; adds "#{breakpoint}:flex-col"
|
10
|
+
def initialize(element: "div", classes: "", vattrs: [], collapse_below: nil)
|
11
|
+
extra_classes = %w[flex flex-wrap]
|
12
|
+
extra_classes << "flex-col #{collapse_below}:flex-row" if collapse_below
|
13
|
+
|
14
|
+
combined_classes = ([classes] + extra_classes).reject(&:empty?).join(" ")
|
15
|
+
|
16
|
+
super(element: element, classes: combined_classes, vattrs: vattrs)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CubitComponents
|
2
|
+
class PageBlockComponent < BaseComponent
|
3
|
+
DEFAULT_OUTER_CLASSES = "px-4 sm:px-6".freeze
|
4
|
+
VALID_VATTRS = Set.new(
|
5
|
+
%w[
|
6
|
+
max-w-3xs max-w-2xs max-w-xs
|
7
|
+
max-w-sm max-w-md max-w-lg
|
8
|
+
max-w-xl max-w-2xl max-w-3xl max-w-4xl max-w-5xl max-w-6xl max-w-7xl
|
9
|
+
max-w-none max-w-full max-w-screen
|
10
|
+
]
|
11
|
+
)
|
12
|
+
|
13
|
+
# Optionally allow extra classes for the inner content block if needed.
|
14
|
+
def initialize(element: "div", classes: "", vattrs: [], content_block_classes: "")
|
15
|
+
@outer_classes = [DEFAULT_OUTER_CLASSES, classes].join(" ").strip
|
16
|
+
@content_block_classes = content_block_classes
|
17
|
+
@content_block_vattrs = vattrs # Pass the vattrs wholesale to the inner content block
|
18
|
+
|
19
|
+
super(element: element, classes: @outer_classes)
|
20
|
+
end
|
21
|
+
|
22
|
+
def call
|
23
|
+
content_tag @element, class: @outer_classes do
|
24
|
+
render_content_block { content }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def render_content_block(&block)
|
31
|
+
# Create a ContentBlockComponent, passing in any inner classes overrides.
|
32
|
+
ContentBlockComponent
|
33
|
+
.new(classes: @content_block_classes, vattrs: @content_block_vattrs)
|
34
|
+
.render_in(view_context, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module CubitComponents
|
2
|
+
class PageComponent < BaseComponent
|
3
|
+
VALID_VATTRS = %w[
|
4
|
+
min-h-screen flex
|
5
|
+
]
|
6
|
+
|
7
|
+
# Use ViewComponent slot for footer.
|
8
|
+
renders_one :footer
|
9
|
+
|
10
|
+
erb_template <<~ERB
|
11
|
+
<<%= @element %> class="<%= classes %>">
|
12
|
+
<div class="grow pb-30<%= ' min-h-screen' if below_fold? %>">
|
13
|
+
<%= content %>
|
14
|
+
</div>
|
15
|
+
<%= footer %>
|
16
|
+
</<%= @element %>>
|
17
|
+
ERB
|
18
|
+
|
19
|
+
# @param footer_position: nil (default) or "below_fold"
|
20
|
+
def initialize(footer_position: nil, classes: "")
|
21
|
+
@footer_position = footer_position
|
22
|
+
@classes = ["flex", "flex-col", "min-h-screen", classes].join(" ")
|
23
|
+
super(element: "div", classes: @classes)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :classes
|
29
|
+
|
30
|
+
def below_fold?
|
31
|
+
@footer_position.to_s == "below_fold"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class BleedComponentPreview < ViewComponent::Preview
|
2
|
+
def default
|
3
|
+
render(CubitComponents::BleedComponent.new(vattrs: ["-m-3"])) do
|
4
|
+
tag.div("Bleed Content", class: "bg-gray-100 p-4")
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def responsive
|
9
|
+
render(CubitComponents::BleedComponent.new(vattrs: ["sm:-mx-2", "md:-m-4"])) do
|
10
|
+
tag.div("Responsive Bleed Content", class: "bg-blue-100 p-4")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class BoxComponentPreview < ViewComponent::Preview
|
4
|
+
def default
|
5
|
+
render(CubitComponents::BoxComponent.new) { "Default Box" }
|
6
|
+
end
|
7
|
+
|
8
|
+
def with_utilities
|
9
|
+
render(CubitComponents::BoxComponent.new(classes: "p-4",
|
10
|
+
vattrs: %w[gap-small text-center])) { "Preview" }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ColumnsComponentPreview < ViewComponent::Preview
|
4
|
+
# Two equal columns, default layout
|
5
|
+
def default
|
6
|
+
render(CubitComponents::ColumnsComponent.new(vattrs: %w[gap-medium])) do
|
7
|
+
tag.div("Col 1", class: "w-1/3") +
|
8
|
+
tag.div("Col 2", class: "w-1/6") +
|
9
|
+
tag.div("Col 3", class: "w-1/2")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Reversed order & collapses below md
|
14
|
+
def reversed_and_collapsing
|
15
|
+
render(CubitComponents::ColumnsComponent.new(reverse: true, collapse_below: "md", vattrs: %w[gap-large items-center])) do
|
16
|
+
tag.div("First", class: "w-full") +
|
17
|
+
tag.div("Second", class: "w-full")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CubitComponents
|
2
|
+
module Previews
|
3
|
+
class ComponentsIndex < ViewComponent::Preview
|
4
|
+
# A listing of all component previews.
|
5
|
+
def index
|
6
|
+
previews = [
|
7
|
+
{name: "BleedComponentPreview", path: "/cubit_components/bleed_component_preview"},
|
8
|
+
{name: "BoxComponentPreview", path: "/cubit_components/box_component_preview"},
|
9
|
+
{name: "ColumnsComponentPreview", path: "/cubit_components/columns_component_preview"},
|
10
|
+
{name: "ContentBlockComponentPreview", path: "/cubit_components/content_block_component_preview"},
|
11
|
+
{name: "InlineComponentPreview", path: "/cubit_components/inline_component_preview"},
|
12
|
+
{name: "PageBlockComponentPreview", path: "/cubit_components/page_block_component_preview"},
|
13
|
+
{name: "PageComponentPreview", path: "/cubit_components/page_component_preview"},
|
14
|
+
{name: "SpreadComponentPreview", path: "/cubit_components/spread_component_preview"},
|
15
|
+
{name: "StackComponentPreview", path: "/cubit_components/stack_component_preview"},
|
16
|
+
{name: "TilesComponentPreview", path: "/cubit_components/tiles_component_preview"}
|
17
|
+
]
|
18
|
+
|
19
|
+
html = "<h1>Components Preview Index</h1><ul>"
|
20
|
+
previews.each do |preview|
|
21
|
+
html << "<li><a href='#{preview[:path]}'>#{preview[:name]}</a></li>"
|
22
|
+
end
|
23
|
+
html << "</ul>"
|
24
|
+
html.html_safe
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class ContentBlockComponentPreview < ViewComponent::Preview
|
2
|
+
def default
|
3
|
+
render(CubitComponents::ContentBlockComponent.new(vattrs: %w[max-w-sm])) do
|
4
|
+
tag.p("This content is constrained by the max-width and centered.")
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def with_extra_classes
|
9
|
+
render(CubitComponents::ContentBlockComponent.new(classes: "bg-gray-100 p-4")) do
|
10
|
+
tag.p("Content block with extra background and padding.")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class InlineComponentPreview < ViewComponent::Preview
|
4
|
+
def default
|
5
|
+
render(CubitComponents::InlineComponent.new(vattrs: ["gap-6"])) do
|
6
|
+
tag.div("Block 1") +
|
7
|
+
tag.div("Block 2") +
|
8
|
+
tag.div("Block 3") +
|
9
|
+
tag.div("Block 4") +
|
10
|
+
tag.div("Block 5") +
|
11
|
+
tag.div("Block 6")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def collapse_below_md
|
16
|
+
render(CubitComponents::InlineComponent.new(collapse_below: "sm", vattrs: ["gap-6"])) do
|
17
|
+
tag.div("Block 1") +
|
18
|
+
tag.div("Block 2") +
|
19
|
+
tag.div("Block 3") +
|
20
|
+
tag.div("Block 4") +
|
21
|
+
tag.div("Block 5") +
|
22
|
+
tag.div("Block 6")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class PageBlockComponentPreview < ViewComponent::Preview
|
2
|
+
def default
|
3
|
+
render(CubitComponents::PageBlockComponent.new(vattrs: %w[max-w-sm])) do
|
4
|
+
tag.p("This is a page block which wraps a content block. Outer padding is applied.")
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def custom_inner
|
9
|
+
render(CubitComponents::PageBlockComponent.new(content_block_classes: "bg-white shadow-lg p-6")) do
|
10
|
+
tag.p("Page block with a customized inner content block.")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class PageComponentPreview < ViewComponent::Preview
|
2
|
+
def default
|
3
|
+
render(CubitComponents::PageComponent.new(footer_position: "screen_bottom", classes: "bg-gray-100")) do |component|
|
4
|
+
component.with_footer { tag.footer("Footer content", class: "p-4 bg-blue-200") }
|
5
|
+
tag.header("Header content", class: "p-4 bg-blue-200") +
|
6
|
+
tag.main("Body content", class: "p-4")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def below_fold
|
11
|
+
render(CubitComponents::PageComponent.new(footer_position: "below_fold", classes: "bg-gray-100")) do |component|
|
12
|
+
component.with_footer { tag.footer("Footer content", class: "p-4 bg-blue-200") }
|
13
|
+
tag.header("Header content", class: "p-4 bg-blue-200") +
|
14
|
+
tag.main("Body content", class: "p-4")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module CubitComponents
|
2
|
+
module Previews
|
3
|
+
module PreviewHelper
|
4
|
+
# Returns a documentation link for the component.
|
5
|
+
def documentation_link(component_name)
|
6
|
+
"https://your.documentation.url/#{component_name}"
|
7
|
+
end
|
8
|
+
|
9
|
+
# Standard documentation render for previews
|
10
|
+
def render_documentation(component_name)
|
11
|
+
render plain: "See documentation: #{documentation_link(component_name)}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class SpreadComponentPreview < ViewComponent::Preview
|
2
|
+
# Default horizontal spread with items-start and text-left
|
3
|
+
def default
|
4
|
+
render(CubitComponents::SpreadComponent.new) do
|
5
|
+
tag.div(class: "p-2 border") { "Item 1" } +
|
6
|
+
tag.div(class: "p-2 border") { "Item 2" } +
|
7
|
+
tag.div(class: "p-2 border") { "Item 3" }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Vertical spread layout
|
12
|
+
def vertical
|
13
|
+
render(CubitComponents::SpreadComponent.new(vattrs: ["vertical", "items-center"])) do
|
14
|
+
tag.div(class: "p-2 border") { "Item 1" } +
|
15
|
+
tag.div(class: "p-2 border") { "Item 2" } +
|
16
|
+
tag.div(class: "p-2 border") { "Item 3" }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Centered items
|
21
|
+
def centered_items
|
22
|
+
render(CubitComponents::SpreadComponent.new(vattrs: ["items-center"])) do
|
23
|
+
tag.div(class: "p-2 border h-16") { "Tall Item" } +
|
24
|
+
tag.div(class: "p-2 border") { "Regular Item" } +
|
25
|
+
tag.div(class: "p-2 border h-12") { "Medium Item" }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Text alignment
|
30
|
+
def text_alignment
|
31
|
+
render(CubitComponents::SpreadComponent.new(vattrs: ["text-center"])) do
|
32
|
+
tag.div(class: "p-2 border w-32") { "Centered Text" } +
|
33
|
+
tag.div(class: "p-2 border w-32") { "Centered Text" } +
|
34
|
+
tag.div(class: "p-2 border w-32") { "Centered Text" }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Custom element
|
39
|
+
def custom_element
|
40
|
+
render(CubitComponents::SpreadComponent.new(element: "section", classes: "border p-4")) do
|
41
|
+
tag.div(class: "p-2 bg-gray-100") { "Section Item 1" } +
|
42
|
+
tag.div(class: "p-2 bg-gray-100") { "Section Item 2" } +
|
43
|
+
tag.div(class: "p-2 bg-gray-100") { "Section Item 3" }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class StackComponentPreview < ViewComponent::Preview
|
4
|
+
# Shows default classes (gap-0 text-left)
|
5
|
+
def default
|
6
|
+
render(CubitComponents::StackComponent.new) do
|
7
|
+
tag.div("Item 1", class: "border") +
|
8
|
+
tag.div("Item 2", class: "border") +
|
9
|
+
tag.div("Item 3", class: "border")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# gap-medium
|
14
|
+
# Overrides gap utility
|
15
|
+
def custom_gap
|
16
|
+
render(CubitComponents::StackComponent.new(vattrs: %w[gap-medium])) do
|
17
|
+
tag.div("Medium gap", class: "border") +
|
18
|
+
tag.div("Medium gap", class: "border") +
|
19
|
+
tag.div("Medium gap", class: "border")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class TilesComponentPreview < ViewComponent::Preview
|
2
|
+
# Default preview: renders three simple tiles.
|
3
|
+
def default
|
4
|
+
render(CubitComponents::TilesComponent.new(vattrs: ["grid-cols-5"])) do
|
5
|
+
tag.div("Tile 1", class: "border p-2") +
|
6
|
+
tag.div("Tile 2", class: "border p-2") +
|
7
|
+
tag.div("Tile 3", class: "border p-2")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Preview with custom grid columns and gap overrides.
|
12
|
+
def custom_grid_and_gap
|
13
|
+
render(CubitComponents::TilesComponent.new(vattrs: ["md:grid-cols-3", "gap-4"])) do
|
14
|
+
tag.div("Tile 1", class: "border p-2") +
|
15
|
+
tag.div("Tile 2", class: "border p-2") +
|
16
|
+
tag.div("Tile 3", class: "border p-2") +
|
17
|
+
tag.div("Tile 4", class: "border p-2")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "debug"
|
2
|
+
|
3
|
+
module CubitComponents
|
4
|
+
class SpreadComponent < BaseComponent
|
5
|
+
VALID_VATTRS = Set.new(
|
6
|
+
%w[
|
7
|
+
items-start items-center items-end
|
8
|
+
text-left text-center text-right
|
9
|
+
flex-col flex-row
|
10
|
+
justify-between
|
11
|
+
]
|
12
|
+
).freeze
|
13
|
+
|
14
|
+
DEFAULT_VATTRS = %w[flex justify-between w-full items-start text-left].freeze
|
15
|
+
|
16
|
+
# @param element [String] HTML element to use
|
17
|
+
# @param classes [String] additional CSS classes
|
18
|
+
# @param vattrs [Array<String>] utility/variant attributes
|
19
|
+
def initialize(element: "div", classes: "", vattrs: [])
|
20
|
+
vattrs = merge_default_vattrs(Array(vattrs))
|
21
|
+
|
22
|
+
extra_classes = []
|
23
|
+
# Add vertical class if specified, otherwise it's horizontal by default
|
24
|
+
if vattrs.include?("vertical")
|
25
|
+
extra_classes << "flex-col"
|
26
|
+
extra_classes << "h-full"
|
27
|
+
vattrs.delete("vertical")
|
28
|
+
else
|
29
|
+
extra_classes << "flex-row"
|
30
|
+
end
|
31
|
+
|
32
|
+
super(
|
33
|
+
element: element,
|
34
|
+
classes: [classes, extra_classes].join(" ").strip,
|
35
|
+
vattrs: vattrs
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def merge_default_vattrs(custom)
|
42
|
+
result = DEFAULT_VATTRS.dup
|
43
|
+
|
44
|
+
# Override defaults with custom values
|
45
|
+
custom.each do |attr|
|
46
|
+
# For each attribute type (items-*, text-*), remove the default if a custom is provided
|
47
|
+
if attr.start_with?("items-")
|
48
|
+
result.delete_if { |v| v.start_with?("items-") }
|
49
|
+
elsif attr.start_with?("text-")
|
50
|
+
result.delete_if { |v| v.start_with?("text-") }
|
51
|
+
end
|
52
|
+
|
53
|
+
result << attr
|
54
|
+
end
|
55
|
+
|
56
|
+
result
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CubitComponents
|
4
|
+
class StackComponent < BaseComponent
|
5
|
+
VALID_VATTRS = Set.new(
|
6
|
+
%w[
|
7
|
+
gap-0 gap-small gap-medium gap-large
|
8
|
+
text-left text-center text-right
|
9
|
+
]
|
10
|
+
).freeze
|
11
|
+
|
12
|
+
DEFAULT_VATTRS = %w[flex flex-col gap-0 text-left].freeze
|
13
|
+
|
14
|
+
# Forward element / classes, but merge-override vattrs
|
15
|
+
def initialize(element: "div", classes: "", vattrs: [])
|
16
|
+
vattrs = merge_default_vattrs(Array(vattrs))
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Replace default gap- or text- related vattrs when caller supplies one
|
23
|
+
def merge_default_vattrs(custom)
|
24
|
+
merged = DEFAULT_VATTRS.dup
|
25
|
+
custom.each do |v|
|
26
|
+
merged.reject! { |d| d.start_with?("gap-") } if v.start_with?("gap-")
|
27
|
+
merged.reject! { |d| d.start_with?("text-") } if v.start_with?("text-")
|
28
|
+
end
|
29
|
+
(merged + custom).uniq
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
Well, the thing I didn't mention is that this is a Rails engine/gem, and the host app can define other custom breakpoints and I need to have tailwind capture those too. I don't think any of the solutions you mentioned can work with that. Correct me if I'm wrong.
|
4
|
+
|
5
|
+
You're absolutely right! Since this is a Rails engine/gem where the host app can define arbitrary custom breakpoints, none of my previous solutions would work. This is a tricky problem because Tailwind needs to know about classes at build time, but you don't know what breakpoints the host app will define.
|
6
|
+
|
7
|
+
Here are some approaches that could work:
|
8
|
+
|
9
|
+
## Solution 1: Host App Responsibility (Recommended)
|
10
|
+
|
11
|
+
Document that host apps need to safelist the dynamic classes themselves:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# In your gem's documentation/README
|
15
|
+
#
|
16
|
+
# If you use custom breakpoints with CubitComponents::ColumnsComponent,
|
17
|
+
# add them to your tailwind.config.js safelist:
|
18
|
+
#
|
19
|
+
# safelist: [
|
20
|
+
# { pattern: /^[a-z]+:flex-row$/ },
|
21
|
+
# // or explicitly:
|
22
|
+
# 'custom-breakpoint:flex-row',
|
23
|
+
# 'another-breakpoint:flex-row'
|
24
|
+
# ]
|
25
|
+
```
|
26
|
+
|
27
|
+
## Solution 2: Configuration Hook
|
28
|
+
|
29
|
+
Provide a configuration option in your engine where host apps can register their breakpoints:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# In your engine
|
33
|
+
module CubitComponents
|
34
|
+
class << self
|
35
|
+
attr_accessor :breakpoints
|
36
|
+
|
37
|
+
def configure
|
38
|
+
yield self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
self.breakpoints = %w[sm md lg xl 2xl]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Host app would do:
|
46
|
+
CubitComponents.configure do |config|
|
47
|
+
config.breakpoints = %w[sm md lg xl 2xl tablet desktop custom]
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
Then provide a rake task or generator that outputs the necessary Tailwind config:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
# lib/tasks/cubit_components_tasks.rake
|
55
|
+
namespace :cubit_components do
|
56
|
+
desc "Generate Tailwind safelist for CubitComponents"
|
57
|
+
task :tailwind_safelist => :environment do
|
58
|
+
breakpoints = CubitComponents.breakpoints
|
59
|
+
safelist = breakpoints.map { |bp| "#{bp}:flex-row" }
|
60
|
+
|
61
|
+
puts "Add this to your tailwind.config.js safelist:"
|
62
|
+
puts safelist.to_json
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
## Solution 3: Always Use Style Attribute
|
68
|
+
|
69
|
+
For truly dynamic values, you might need to bypass Tailwind classes entirely:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
private
|
73
|
+
|
74
|
+
def build_layout_classes(user)
|
75
|
+
base = %w[flex]
|
76
|
+
base << if @collapse_below && @reverse
|
77
|
+
"flex-col-reverse"
|
78
|
+
elsif @collapse_below
|
79
|
+
"flex-col"
|
80
|
+
else
|
81
|
+
"flex-row"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Store the breakpoint for later use in style attribute
|
85
|
+
@responsive_style = @collapse_below
|
86
|
+
|
87
|
+
(base + user.to_s.split).join(" ")
|
88
|
+
end
|
89
|
+
|
90
|
+
# Then in your component's template, use a media query
|
91
|
+
# This would require passing the actual breakpoint value (e.g., "768px")
|
92
|
+
```
|
93
|
+
|
94
|
+
## Solution 4: JIT Arbitrary Value Support
|
95
|
+
|
96
|
+
If the host app uses Tailwind JIT mode, you could potentially use arbitrary values, but this still requires the host app to configure their breakpoints properly.
|
97
|
+
|
98
|
+
## Recommendation
|
99
|
+
|
100
|
+
I'd recommend **Solution 1** - making it the host app's responsibility to safelist the classes they need. This is the most common pattern for UI component libraries. You could make it easier by:
|
101
|
+
|
102
|
+
1. Providing clear documentation
|
103
|
+
2. Offering a generator that creates a partial Tailwind config
|
104
|
+
3. Providing common patterns they can use
|
105
|
+
|
106
|
+
Example generator:
|
107
|
+
```ruby
|
108
|
+
# lib/generators/cubit_components/tailwind_generator.rb
|
109
|
+
module CubitComponents
|
110
|
+
class TailwindGenerator < Rails::Generators::Base
|
111
|
+
def create_safelist_config
|
112
|
+
create_file "config/cubit_components_tailwind.js", <<~JS
|
113
|
+
// Add this to your tailwind.config.js safelist array
|
114
|
+
module.exports = {
|
115
|
+
// Safelist pattern for all breakpoint:flex-row combinations
|
116
|
+
pattern: /^[a-z0-9]+:flex-row$/,
|
117
|
+
|
118
|
+
// Or if you prefer explicit listing:
|
119
|
+
// 'your-custom-breakpoint:flex-row',
|
120
|
+
}
|
121
|
+
JS
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
This way, you're being transparent about the requirement while giving host apps full control over their Tailwind configuration.
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module CubitComponents
|
2
|
+
class TilesComponent < BaseComponent
|
3
|
+
# Allowed vattrs for grid columns and gap utilities.
|
4
|
+
VALID_VATTRS = Set.new(
|
5
|
+
begin
|
6
|
+
valid = []
|
7
|
+
breakpoints = ["", "sm:", "md:", "lg:", "xl:"]
|
8
|
+
cols = %w[none 1 2 3 4 5]
|
9
|
+
breakpoints.each do |bp|
|
10
|
+
cols.each { |col| valid << "#{bp}grid-cols-#{col}" }
|
11
|
+
end
|
12
|
+
gap_values = %w[0 1 2 3 4 5]
|
13
|
+
gap_values.each { |g| valid << "gap-#{g}" }
|
14
|
+
valid
|
15
|
+
end
|
16
|
+
).freeze
|
17
|
+
|
18
|
+
DEFAULT_VATTRS = %w[grid-cols-1 gap-0].freeze
|
19
|
+
|
20
|
+
def initialize(element: "div", classes: "", vattrs: [])
|
21
|
+
# Merge defaults (grid cols and gap) with any provided overrides.
|
22
|
+
vattrs = merge_default_vattrs(Array(vattrs))
|
23
|
+
# Ensure grid display is always present.
|
24
|
+
classes = [classes, "grid"].join(" ").strip
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def merge_default_vattrs(custom)
|
31
|
+
result = DEFAULT_VATTRS.dup
|
32
|
+
custom.each do |attr|
|
33
|
+
if attr.start_with?("grid-cols-")
|
34
|
+
result.reject! { |v| v.start_with?("grid-cols-") }
|
35
|
+
elsif attr.start_with?("gap-")
|
36
|
+
result.reject! { |v| v.start_with?("gap-") }
|
37
|
+
end
|
38
|
+
result << attr
|
39
|
+
end
|
40
|
+
result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module CubitComponents
|
2
|
+
module VattrValidator
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class InvalidVattrError < ArgumentError; end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def validate_vattrs(vattrs, allowed_vattrs, component_name: self.class.name)
|
10
|
+
return if Rails.env.production?
|
11
|
+
|
12
|
+
invalid_vattrs = Array(vattrs) - Array(allowed_vattrs)
|
13
|
+
return if invalid_vattrs.empty?
|
14
|
+
|
15
|
+
case CubitComponents.configuration.invalid_vattr_behavior
|
16
|
+
when :raise
|
17
|
+
handle_invalid_vattrs_with_exception(invalid_vattrs, allowed_vattrs, component_name)
|
18
|
+
when :warn
|
19
|
+
handle_invalid_vattrs_with_warning(invalid_vattrs, allowed_vattrs, component_name)
|
20
|
+
when :silent
|
21
|
+
# Do nothing
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle_invalid_vattrs_with_exception(invalid_vattrs, allowed_vattrs, component_name)
|
26
|
+
message = build_error_message(invalid_vattrs, allowed_vattrs, component_name)
|
27
|
+
raise InvalidVattrError, message
|
28
|
+
end
|
29
|
+
|
30
|
+
def handle_invalid_vattrs_with_warning(invalid_vattrs, allowed_vattrs, component_name)
|
31
|
+
message = build_error_message(invalid_vattrs, allowed_vattrs, component_name)
|
32
|
+
|
33
|
+
# Use uplevel to show the warning at the call site
|
34
|
+
Kernel.warn("\n#{message}", uplevel: 3)
|
35
|
+
|
36
|
+
# Also log with color if Rails logger is available
|
37
|
+
if defined?(Rails.logger) && Rails.logger
|
38
|
+
Rails.logger.warn("\e[33m#{message}\e[0m")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_error_message(invalid_vattrs, allowed_vattrs, component_name)
|
43
|
+
<<~MESSAGE
|
44
|
+
⚠️ [CubitComponents] Invalid vattrs for #{component_name}:
|
45
|
+
Invalid: #{invalid_vattrs.join(", ")}
|
46
|
+
|
47
|
+
Allowed vattrs: #{allowed_vattrs.join(", ")}
|
48
|
+
|
49
|
+
These vattrs will have no effect on the component.
|
50
|
+
MESSAGE
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "cubit_components/version"
|
2
|
+
require "cubit_components/engine"
|
3
|
+
|
4
|
+
# components
|
5
|
+
require_relative "cubit_components/base_component"
|
6
|
+
require_relative "cubit_components/box_component"
|
7
|
+
require_relative "cubit_components/stack_component"
|
8
|
+
require_relative "cubit_components/inline_component"
|
9
|
+
require_relative "cubit_components/columns_component"
|
10
|
+
require_relative "cubit_components/spread_component"
|
11
|
+
require_relative "cubit_components/tiles_component"
|
12
|
+
require_relative "cubit_components/content_block_component"
|
13
|
+
require_relative "cubit_components/page_block_component"
|
14
|
+
require_relative "cubit_components/page_component"
|
15
|
+
require_relative "cubit_components/bleed_component"
|
16
|
+
require_relative "cubit_components/configuration"
|
17
|
+
|
18
|
+
module CubitComponents
|
19
|
+
class << self
|
20
|
+
attr_writer :configuration
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configuration
|
24
|
+
@configuration ||= Configuration.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.configure
|
28
|
+
yield(configuration)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.reset_configuration!
|
32
|
+
@configuration = Configuration.new
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "rails/generators" if defined?(Rails::Generators)
|
2
|
+
|
3
|
+
module Cubit
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path("../templates", __FILE__)
|
6
|
+
|
7
|
+
def install
|
8
|
+
say "Installing Cubit Components...", :green
|
9
|
+
|
10
|
+
add_tailwind_import
|
11
|
+
verify_installation
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def add_tailwind_import
|
17
|
+
target_file = "app/assets/tailwind/application.css"
|
18
|
+
|
19
|
+
if !File.exist?(target_file)
|
20
|
+
say "Could not find Tailwind CSS file to import into. Please manually add:", :red
|
21
|
+
say '@import "../builds/tailwind/cubit_components.css";', :yellow
|
22
|
+
say <<~TW_VERSION_WARNING, :red
|
23
|
+
⚠️ Warning: Tailwind CSS file not found at #{target_file}.
|
24
|
+
Please ensure you have Tailwind CSS version 4.0 or higher installed and configured correctly and that you are using a CSS config file rather than `tailwind.config.js`.
|
25
|
+
TW_VERSION_WARNING
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
content = File.read(target_file)
|
30
|
+
import_line = '@import "../builds/tailwind/cubit_components.css";'
|
31
|
+
|
32
|
+
if content.include?(import_line)
|
33
|
+
say "CubitComponents is already imported in #{target_file}", :yellow
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
if content.include?('@import "tailwindcss";')
|
38
|
+
inject_into_file target_file, after: '@import "tailwindcss";' do
|
39
|
+
"\n#{import_line}"
|
40
|
+
end
|
41
|
+
else
|
42
|
+
append_to_file target_file do
|
43
|
+
"\n#{import_line}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
say "Added CubitComponents import to #{target_file}", :green
|
48
|
+
end
|
49
|
+
|
50
|
+
def verify_installation
|
51
|
+
say "\nVerifying installation...", :blue
|
52
|
+
|
53
|
+
# Check if any Tailwind file contains our import
|
54
|
+
tailwind_files = Dir.glob("app/assets/{stylesheets,tailwind}/*.css")
|
55
|
+
unless tailwind_files.any? { |f| File.read(f).include?("cubit_components") }
|
56
|
+
say "⚠️ Warning: Cubit Components import not found in any CSS files", :red
|
57
|
+
say "Please manually add @import \"../builds/tailwind/cubit_components.css\"; to your main CSS file", :yellow
|
58
|
+
end
|
59
|
+
|
60
|
+
say "✅ Verification complete", :green
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
metadata
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cubit_components
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Fabio Papa
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: propshaft
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '1.0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '1.0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rails
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '8.0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '8.0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: tailwindcss-rails
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '4.0'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '4.0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: view_component
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.0'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: capybara
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.38'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.38'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: rspec-its
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '2.0'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '2.0'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: rspec-rails
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '6.0'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '6.0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: standardrb
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '1.0'
|
117
|
+
type: :development
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '1.0'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: standard-rails
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '1.4'
|
131
|
+
type: :development
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '1.4'
|
138
|
+
description: CubitComponents provides a set of layout-focused ViewComponents with
|
139
|
+
built-in Tailwind CSS support and utility class validation.
|
140
|
+
email:
|
141
|
+
- me@fabiopapa.dev
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- MIT-LICENSE
|
147
|
+
- README.md
|
148
|
+
- Rakefile
|
149
|
+
- app/assets/stylesheets/cubit_components/application.css
|
150
|
+
- app/assets/tailwind/cubit_components/engine.css
|
151
|
+
- config/routes.rb
|
152
|
+
- lib/cubit_components.rb
|
153
|
+
- lib/cubit_components/base_component.rb
|
154
|
+
- lib/cubit_components/bleed_component.rb
|
155
|
+
- lib/cubit_components/box_component.rb
|
156
|
+
- lib/cubit_components/columns_component.rb
|
157
|
+
- lib/cubit_components/configuration.rb
|
158
|
+
- lib/cubit_components/content_block_component.rb
|
159
|
+
- lib/cubit_components/engine.rb
|
160
|
+
- lib/cubit_components/inline_component.rb
|
161
|
+
- lib/cubit_components/page_block_component.rb
|
162
|
+
- lib/cubit_components/page_component.rb
|
163
|
+
- lib/cubit_components/previews/bleed_component_preview.rb
|
164
|
+
- lib/cubit_components/previews/box_component_preview.rb
|
165
|
+
- lib/cubit_components/previews/columns_component_preview.rb
|
166
|
+
- lib/cubit_components/previews/components_index.rb
|
167
|
+
- lib/cubit_components/previews/content_block_component_preview.rb
|
168
|
+
- lib/cubit_components/previews/inline_component_preview.rb
|
169
|
+
- lib/cubit_components/previews/page_block_component_preview.rb
|
170
|
+
- lib/cubit_components/previews/page_component_preview.rb
|
171
|
+
- lib/cubit_components/previews/preview_helper.rb
|
172
|
+
- lib/cubit_components/previews/spread_component_preview.rb
|
173
|
+
- lib/cubit_components/previews/stack_component_preview.rb
|
174
|
+
- lib/cubit_components/previews/tiles_component_preview.rb
|
175
|
+
- lib/cubit_components/spread_component.rb
|
176
|
+
- lib/cubit_components/stack_component.rb
|
177
|
+
- lib/cubit_components/tailwind_generated_classes_chat.txt
|
178
|
+
- lib/cubit_components/tiles_component.rb
|
179
|
+
- lib/cubit_components/vattr_validator.rb
|
180
|
+
- lib/cubit_components/version.rb
|
181
|
+
- lib/generators/cubit/install/install_generator.rb
|
182
|
+
- lib/tasks/cubit_components_tasks.rake
|
183
|
+
homepage: https://github.com/fapapa/cubit_components
|
184
|
+
licenses:
|
185
|
+
- MIT
|
186
|
+
metadata:
|
187
|
+
homepage_uri: https://github.com/fapapa/cubit_components
|
188
|
+
source_code_uri: https://github.com/fapapa/cubit_components
|
189
|
+
changelog_uri: https://github.com/fapapa/cubit_components/blob/main/CHANGELOG.md
|
190
|
+
rdoc_options: []
|
191
|
+
require_paths:
|
192
|
+
- lib
|
193
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - ">="
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
199
|
+
requirements:
|
200
|
+
- - ">="
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
requirements: []
|
204
|
+
rubygems_version: 3.6.8
|
205
|
+
specification_version: 4
|
206
|
+
summary: A collection of reusable ViewComponents with standardized styling and utility
|
207
|
+
class validation.
|
208
|
+
test_files: []
|