diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ffdfba..4b02706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). * Button component (first component, wee!) * Input components * InputField components +* Basic Card component ### Changes diff --git a/README.md b/README.md index a357dbc..de63077 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,33 @@ Add Flowbite to your Tailwind CSS configuration. In your `app/assets/tailwind/ap ) %> ``` +## How to customize components + +### Add specific CSS classes + +A common use case for customizing a component is to add more CSS classes when +rendering it, fx to change the size or spacing. flowbite-components is optimized +for this case and all you need to do is specify the extra classes: + +```erb +<%= render(Flowbite::Card.new(class: "w-full my-8")) { "Content" } %> +``` +renders +```html +
+``` + +If you want to fully replace the existing classes, you can pass an entirely new +`class` attribute via options: + +```erb +<%= render(Flowbite::Card.new(options: {class: "w-full my-8"})) { "Content" } %> +``` +renders +```html +
+``` + ## Available Components ### Form Components diff --git a/app/components/flowbite/card.rb b/app/components/flowbite/card.rb new file mode 100644 index 0000000..1035a5a --- /dev/null +++ b/app/components/flowbite/card.rb @@ -0,0 +1,43 @@ +module Flowbite + # Renders a card element. + # + # See https://flowbite.com/docs/components/cards/ + class Card < ViewComponent::Base + class << self + def classes(state: :default, style: :default) + style = styles.fetch(style) + style.fetch(state) + end + + # rubocop:disable Layout/LineLength + def styles + { + default: Flowbite::Style.new( + default: ["max-w-sm", "p-6", "bg-white", "border", "border-gray-200", "rounded-lg", "shadow-sm", "dark:bg-gray-800", "dark:border-gray-700"] + ) + }.freeze + end + # rubocop:enable Layout/LineLength + end + + def call + card_options = {} + card_options[:class] = self.class.classes + @class + + content_tag(:div, card_options.merge(@options)) do + concat(content_tag(:div, content, class: "font-normal text-gray-700 dark:text-gray-400")) + end + end + + # @param class [Array] Additional CSS classes for the card + # container. + # + # @param options [Hash] Additional HTML options for the card container + # (e.g., custom classes, data attributes). These options are merged into + # the card's root element. + def initialize(class: [], options: {}) + @class = Array(binding.local_variable_get(:class)) || [] + @options = options || {} + end + end +end diff --git a/demo/app/views/pages/index.html.erb b/demo/app/views/pages/index.html.erb index 9c77fc3..a52be2e 100644 --- a/demo/app/views/pages/index.html.erb +++ b/demo/app/views/pages/index.html.erb @@ -17,7 +17,7 @@
-
+ <%= render(Flowbite::Card.new(:class => "sm:flex-1")) do %> @@ -30,9 +30,9 @@ <% end %> -
+ <% end %> -
+ <%= render(Flowbite::Card.new(:class => "sm:flex-1")) do %> @@ -46,9 +46,9 @@ <% end %> -
+ <% end %> -
+ <%= render(Flowbite::Card.new(:class => "sm:flex-1")) do %> @@ -64,5 +64,5 @@ <% end %> -
+ <% end %>
diff --git a/demo/test/components/previews/card_preview.rb b/demo/test/components/previews/card_preview.rb new file mode 100644 index 0000000..f2b44f0 --- /dev/null +++ b/demo/test/components/previews/card_preview.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class CardPreview < Lookbook::Preview + # Use the following simple card component with a title and description. + def default + render(Flowbite::Card.new) { "Use the following simple card component with a title and description." } + end +end diff --git a/test/components/flowbite/card_test.rb b/test/components/flowbite/card_test.rb new file mode 100644 index 0000000..e334dcd --- /dev/null +++ b/test/components/flowbite/card_test.rb @@ -0,0 +1,30 @@ +require "test_helper" + +class Flowbite::CardTest < Minitest::Test + include ViewComponent::TestHelpers + + def test_renders_a_default_card + render_inline(Flowbite::Card.new) { "Card Content" } + + assert_selector("div.p-6.bg-white.border.border-gray-200.rounded-lg.shadow-sm") + end + + def test_passes_options_to_the_card_as_attributes + render_inline(Flowbite::Card.new(options: {id: "card-1"})) { "Card Content" } + + assert_selector("div#card-1") + end + + def test_adds_the_classes_to_the_default_classes + render_inline(Flowbite::Card.new(class: "custom-class another")) { "Card Content" } + + assert_selector("div.p-6.bg-white.border.border-gray-200.rounded-lg.shadow-sm.custom-class.another") + end + + def test_overrides_the_default_classes + render_inline(Flowbite::Card.new(options: {class: "custom-class another"})) { "Card Content" } + + assert_no_selector("div.p-6.bg-white.border.border-gray-200.rounded-lg.shadow-sm") + assert_selector("div.custom-class.another") + end +end