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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +58 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/stylesheets/cubit_components/application.css +6 -0
  6. data/app/assets/tailwind/cubit_components/engine.css +4 -0
  7. data/config/routes.rb +2 -0
  8. data/lib/cubit_components/base_component.rb +39 -0
  9. data/lib/cubit_components/bleed_component.rb +26 -0
  10. data/lib/cubit_components/box_component.rb +8 -0
  11. data/lib/cubit_components/columns_component.rb +48 -0
  12. data/lib/cubit_components/configuration.rb +18 -0
  13. data/lib/cubit_components/content_block_component.rb +21 -0
  14. data/lib/cubit_components/engine.rb +14 -0
  15. data/lib/cubit_components/inline_component.rb +19 -0
  16. data/lib/cubit_components/page_block_component.rb +37 -0
  17. data/lib/cubit_components/page_component.rb +34 -0
  18. data/lib/cubit_components/previews/bleed_component_preview.rb +13 -0
  19. data/lib/cubit_components/previews/box_component_preview.rb +12 -0
  20. data/lib/cubit_components/previews/columns_component_preview.rb +20 -0
  21. data/lib/cubit_components/previews/components_index.rb +28 -0
  22. data/lib/cubit_components/previews/content_block_component_preview.rb +13 -0
  23. data/lib/cubit_components/previews/inline_component_preview.rb +25 -0
  24. data/lib/cubit_components/previews/page_block_component_preview.rb +13 -0
  25. data/lib/cubit_components/previews/page_component_preview.rb +17 -0
  26. data/lib/cubit_components/previews/preview_helper.rb +15 -0
  27. data/lib/cubit_components/previews/spread_component_preview.rb +46 -0
  28. data/lib/cubit_components/previews/stack_component_preview.rb +22 -0
  29. data/lib/cubit_components/previews/tiles_component_preview.rb +20 -0
  30. data/lib/cubit_components/spread_component.rb +59 -0
  31. data/lib/cubit_components/stack_component.rb +32 -0
  32. data/lib/cubit_components/tailwind_generated_classes_chat.txt +127 -0
  33. data/lib/cubit_components/tiles_component.rb +43 -0
  34. data/lib/cubit_components/vattr_validator.rb +53 -0
  35. data/lib/cubit_components/version.rb +3 -0
  36. data/lib/cubit_components.rb +34 -0
  37. data/lib/generators/cubit/install/install_generator.rb +63 -0
  38. data/lib/tasks/cubit_components_tasks.rake +4 -0
  39. 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
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1,6 @@
1
+ /*
2
+ *= require_tree .
3
+ *= require_self
4
+ */
5
+
6
+ @import "tailwindcss";
@@ -0,0 +1,4 @@
1
+ @import "tailwindcss";
2
+
3
+ @source inline("{sm:,md:,lg:,xl:,2xl:,}flex-row");
4
+ @source "../../../../lib/**/*";
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CubitComponents
4
+ class BoxComponent < BaseComponent
5
+ # Accept every vattr ⇒ disable validation
6
+ private def validate_vattrs? = false
7
+ end
8
+ 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,3 @@
1
+ module CubitComponents
2
+ VERSION = "0.1.0"
3
+ 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
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :cubit_components do
3
+ # # Task goes here
4
+ # 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: []