Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added

* Flowbite::Link component to render links.
* Flowbite::Card now displays a title via the title argument/slot.

### Changed

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,13 @@ renders
- **Outline Button**: `Flowbite::Button::Outline`
- **Pill Button**: `Flowbite::Button::Pill`

#### Cards
- **Card**: `Flowbite::Card` (default card with content and title)

#### Navigation
- **Link**: `Flowbite::Link` (default link styling)


## Development

After checking out the repo, run `bin/setup` to install dependencies.
Expand Down
59 changes: 49 additions & 10 deletions app/components/flowbite/card.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module Flowbite
#
# See https://flowbite.com/docs/components/cards/
class Card < ViewComponent::Base
renders_one :title

class << self
def classes(state: :default, style: :default)
style = styles.fetch(style)
Expand All @@ -22,24 +24,61 @@ def styles
# 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<String>] 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: {})
#
# @param title [Hash] An optional title for the card. If provided,
# it will be rendered at the top of the card in a h5 tag using the
# Card::Title component. The hash can contain:
# - `content`: The text content of the title
# - `options`: Additional HTML options to pass to the title element
# Alternatively, you can use the `title` slot to provide the entire
# title element yourself.
def initialize(class: [], options: {}, title: {})
@class = Array(binding.local_variable_get(:class)) || []
@options = options || {}
@title = title
end

protected

def card_options
card_options = {}
card_options[:class] = self.class.classes + @class
card_options.merge(@options)
end

# Returns the HTML to use for the title element if any
def default_title
component = Flowbite::Card::Title.new(**default_title_options)

if default_title_content
component.with_content(default_title_content)
else
component
end

render(component)
end

def default_title_content
return nil unless @title

@title[:content]
end

# @return [Hash] The options to pass to the default title component
def default_title_options
title_options = @title.dup
title_options[:options] || {}
end

def title?
@title.present?
end
end
end
6 changes: 6 additions & 0 deletions app/components/flowbite/card/card.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<%= content_tag(:div, card_options) do %>
<%= title %>
<% if content.present? %>
<div class="font-normal text-gray-700 dark:text-gray-400"><%= content %></div>
<% end %>
<% end %>
37 changes: 37 additions & 0 deletions app/components/flowbite/card/title.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module Flowbite
class Card
# Renders the title of a card element.
class Title < 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: ["mb-2", "text-2xl", "font-bold", "tracking-tight", "text-gray-900", "dark:text-white"]
)
}.freeze
end
# rubocop:enable Layout/LineLength
end

def call
title_options = {
class: self.class.classes
}.merge(@options)

content_tag(:h5, content, **title_options)
end

def initialize(**options)
@options = options || {}
end
end
end
end
17 changes: 17 additions & 0 deletions demo/test/components/previews/card_preview.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
# frozen_string_literal: true

class CardPreview < Lookbook::Preview
# @!group Default card

# 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

def with_title_argument
render(Flowbite::Card.new(title: {content: "Card Title"})) do
"This card includes a title with the default styling."
end
end

def with_title_slot
render(Flowbite::Card.new) do |component|
component.with_title { "<h1 class=\"text-3xl\">This title replaces the entire title element</h1>".html_safe }
"Use the title slot to control all aspects of the title element"
end
end

# @!endgroup
end
21 changes: 21 additions & 0 deletions test/components/flowbite/card_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,25 @@ def test_overrides_the_default_classes
assert_no_selector("div.p-6.bg-white.border.border-gray-200.rounded-lg.shadow-sm")
assert_selector("div.custom-class.another")
end

def test_with_title_argument
render_inline(Flowbite::Card.new(title: {content: "Card Title"})) { "Card Content" }

assert_selector("h5.mb-2.text-2xl.font-bold.tracking-tight.text-gray-900.dark\\:text-white", text: "Card Title")
end

def test_passes_title_options_to_the_title
render_inline(Flowbite::Card.new(title: {content: "Card Title", options: {class: "custom-title-class"}})) { "Card Content" }

assert_selector("h5.custom-title-class", text: "Card Title")
end

def test_with_title_slot_when_using_with_title
render_inline(Flowbite::Card.new) do |component|
component.with_title { "<h1>This is the full title</h1>".html_safe }
end

assert_no_selector("h5.mb-2.text-2xl.font-bold.tracking-tight.text-gray-900.dark\\:text-white")
assert_selector("h1", text: "This is the full title")
end
end