Skip to content

Commit 195ae67

Browse files
authored
Merge pull request #46 from substancelab/card_titles
Add titles to cards
2 parents 6f0ce42 + c0277e1 commit 195ae67

File tree

7 files changed

+135
-10
lines changed

7 files changed

+135
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
88
### Added
99

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

1213
### Changed
1314

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,13 @@ renders
217217
- **Outline Button**: `Flowbite::Button::Outline`
218218
- **Pill Button**: `Flowbite::Button::Pill`
219219

220+
#### Cards
221+
- **Card**: `Flowbite::Card` (default card with content and title)
222+
220223
#### Navigation
221224
- **Link**: `Flowbite::Link` (default link styling)
222225

226+
223227
## Development
224228

225229
After checking out the repo, run `bin/setup` to install dependencies.

app/components/flowbite/card.rb

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ module Flowbite
55
#
66
# See https://flowbite.com/docs/components/cards/
77
class Card < ViewComponent::Base
8+
renders_one :title
9+
810
class << self
911
def classes(state: :default, style: :default)
1012
style = styles.fetch(style)
@@ -22,24 +24,61 @@ def styles
2224
# rubocop:enable Layout/LineLength
2325
end
2426

25-
def call
26-
card_options = {}
27-
card_options[:class] = self.class.classes + @class
28-
29-
content_tag(:div, card_options.merge(@options)) do
30-
concat(content_tag(:div, content, class: "font-normal text-gray-700 dark:text-gray-400"))
31-
end
32-
end
33-
3427
# @param class [Array<String>] Additional CSS classes for the card
3528
# container.
3629
#
3730
# @param options [Hash] Additional HTML options for the card container
3831
# (e.g., custom classes, data attributes). These options are merged into
3932
# the card's root element.
40-
def initialize(class: [], options: {})
33+
#
34+
# @param title [Hash] An optional title for the card. If provided,
35+
# it will be rendered at the top of the card in a h5 tag using the
36+
# Card::Title component. The hash can contain:
37+
# - `content`: The text content of the title
38+
# - `options`: Additional HTML options to pass to the title element
39+
# Alternatively, you can use the `title` slot to provide the entire
40+
# title element yourself.
41+
def initialize(class: [], options: {}, title: {})
4142
@class = Array(binding.local_variable_get(:class)) || []
4243
@options = options || {}
44+
@title = title
45+
end
46+
47+
protected
48+
49+
def card_options
50+
card_options = {}
51+
card_options[:class] = self.class.classes + @class
52+
card_options.merge(@options)
53+
end
54+
55+
# Returns the HTML to use for the title element if any
56+
def default_title
57+
component = Flowbite::Card::Title.new(**default_title_options)
58+
59+
if default_title_content
60+
component.with_content(default_title_content)
61+
else
62+
component
63+
end
64+
65+
render(component)
66+
end
67+
68+
def default_title_content
69+
return nil unless @title
70+
71+
@title[:content]
72+
end
73+
74+
# @return [Hash] The options to pass to the default title component
75+
def default_title_options
76+
title_options = @title.dup
77+
title_options[:options] || {}
78+
end
79+
80+
def title?
81+
@title.present?
4382
end
4483
end
4584
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<%= content_tag(:div, card_options) do %>
2+
<%= title %>
3+
<% if content.present? %>
4+
<div class="font-normal text-gray-700 dark:text-gray-400"><%= content %></div>
5+
<% end %>
6+
<% end %>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
module Flowbite
4+
class Card
5+
# Renders the title of a card element.
6+
class Title < ViewComponent::Base
7+
class << self
8+
def classes(state: :default, style: :default)
9+
style = styles.fetch(style)
10+
style.fetch(state)
11+
end
12+
13+
# rubocop:disable Layout/LineLength
14+
def styles
15+
{
16+
default: Flowbite::Style.new(
17+
default: ["mb-2", "text-2xl", "font-bold", "tracking-tight", "text-gray-900", "dark:text-white"]
18+
)
19+
}.freeze
20+
end
21+
# rubocop:enable Layout/LineLength
22+
end
23+
24+
def call
25+
title_options = {
26+
class: self.class.classes
27+
}.merge(@options)
28+
29+
content_tag(:h5, content, **title_options)
30+
end
31+
32+
def initialize(**options)
33+
@options = options || {}
34+
end
35+
end
36+
end
37+
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
11
# frozen_string_literal: true
22

33
class CardPreview < Lookbook::Preview
4+
# @!group Default card
5+
46
# Use the following simple card component with a title and description.
57
def default
68
render(Flowbite::Card.new) { "Use the following simple card component with a title and description." }
79
end
10+
11+
def with_title_argument
12+
render(Flowbite::Card.new(title: {content: "Card Title"})) do
13+
"This card includes a title with the default styling."
14+
end
15+
end
16+
17+
def with_title_slot
18+
render(Flowbite::Card.new) do |component|
19+
component.with_title { "<h1 class=\"text-3xl\">This title replaces the entire title element</h1>".html_safe }
20+
"Use the title slot to control all aspects of the title element"
21+
end
22+
end
23+
24+
# @!endgroup
825
end

test/components/flowbite/card_test.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,25 @@ def test_overrides_the_default_classes
2727
assert_no_selector("div.p-6.bg-white.border.border-gray-200.rounded-lg.shadow-sm")
2828
assert_selector("div.custom-class.another")
2929
end
30+
31+
def test_with_title_argument
32+
render_inline(Flowbite::Card.new(title: {content: "Card Title"})) { "Card Content" }
33+
34+
assert_selector("h5.mb-2.text-2xl.font-bold.tracking-tight.text-gray-900.dark\\:text-white", text: "Card Title")
35+
end
36+
37+
def test_passes_title_options_to_the_title
38+
render_inline(Flowbite::Card.new(title: {content: "Card Title", options: {class: "custom-title-class"}})) { "Card Content" }
39+
40+
assert_selector("h5.custom-title-class", text: "Card Title")
41+
end
42+
43+
def test_with_title_slot_when_using_with_title
44+
render_inline(Flowbite::Card.new) do |component|
45+
component.with_title { "<h1>This is the full title</h1>".html_safe }
46+
end
47+
48+
assert_no_selector("h5.mb-2.text-2xl.font-bold.tracking-tight.text-gray-900.dark\\:text-white")
49+
assert_selector("h1", text: "This is the full title")
50+
end
3051
end

0 commit comments

Comments
 (0)