Skip to content

Commit e4c5e2c

Browse files
authored
Merge pull request #82 from substancelab/koppen/badge-components
Add Badge component
2 parents 44a14bc + 5ddc5af commit e4c5e2c

File tree

10 files changed

+568
-0
lines changed

10 files changed

+568
-0
lines changed

CHANGELOG.md

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

88
### Added
99

10+
* Badge component.
1011
* Sidebar component.
1112

1213
### Changed

app/components/flowbite/badge.rb

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# frozen_string_literal: true
2+
3+
module Flowbite
4+
# Renders a badge component for displaying labels, counts, or status
5+
# indicators.
6+
#
7+
# @example Basic usage
8+
# <%= render(Flowbite::Badge.new) { "Default" } %>
9+
#
10+
# @example With border
11+
# <%= render(Flowbite::Badge.new(bordered: true, style: :success)) { "Success" } %>
12+
#
13+
# @see https://flowbite.com/docs/components/badge/
14+
# @lookbook_embed BadgePreview
15+
class Badge < ViewComponent::Base
16+
SIZES = {
17+
default: ["text-xs", "font-medium", "px-1.5", "py-0.5"],
18+
lg: ["text-sm", "font-medium", "px-2", "py-1"]
19+
}.freeze
20+
21+
BORDER_CLASSES = {
22+
alternative: ["border", "border-default"],
23+
brand: ["border", "border-brand-subtle"],
24+
danger: ["border", "border-danger-subtle"],
25+
gray: ["border", "border-default-medium"],
26+
success: ["border", "border-success-subtle"],
27+
warning: ["border", "border-warning-subtle"]
28+
}.freeze
29+
30+
class << self
31+
def classes(size: :default, state: :default, style: :brand)
32+
styles.fetch(style).fetch(state) + sizes.fetch(size)
33+
end
34+
35+
def sizes
36+
SIZES
37+
end
38+
39+
# rubocop:disable Layout/LineLength
40+
def styles
41+
Flowbite::Styles.from_hash({
42+
alternative: {
43+
default: ["bg-neutral-primary-soft", "hover:bg-neutral-secondary-medium", "rounded", "text-heading"]
44+
},
45+
brand: {
46+
default: ["bg-brand-softer", "hover:bg-brand-soft", "rounded", "text-fg-brand-strong"]
47+
},
48+
danger: {
49+
default: ["bg-danger-soft", "hover:bg-danger-medium", "rounded", "text-fg-danger-strong"]
50+
},
51+
gray: {
52+
default: ["bg-neutral-secondary-medium", "hover:bg-neutral-tertiary-medium", "rounded", "text-heading"]
53+
},
54+
success: {
55+
default: ["bg-success-soft", "hover:bg-success-medium", "rounded", "text-fg-success-strong"]
56+
},
57+
warning: {
58+
default: ["bg-warning-soft", "hover:bg-warning-medium", "rounded", "text-fg-warning"]
59+
}
60+
}.freeze)
61+
end
62+
# rubocop:enable Layout/LineLength
63+
end
64+
65+
attr_reader :options
66+
67+
# @param bordered [Boolean] Whether to add a border to the badge.
68+
# @param class [String, Array<String>] Additional CSS classes.
69+
# @param dot [Boolean] Whether to show a dot indicator.
70+
# @param href [String] If provided, renders the badge as a link.
71+
# @param size [Symbol] The size of the badge (:default or :lg).
72+
# @param style [Symbol] The color style (:alternative, :brand, :danger,
73+
# :gray, :success, :warning).
74+
def initialize(bordered: false, class: nil, dot: false, href: nil,
75+
size: :default, style: :brand, **options)
76+
@bordered = bordered
77+
@class = Array.wrap(binding.local_variable_get(:class))
78+
@dot = dot
79+
@href = href
80+
@size = size
81+
@style = style
82+
@options = options
83+
end
84+
85+
def bordered?
86+
!!@bordered
87+
end
88+
89+
def dot?
90+
!!@dot
91+
end
92+
93+
def link?
94+
@href.present?
95+
end
96+
97+
private
98+
99+
def classes
100+
result = self.class.classes(size: @size, state: :default, style: @style)
101+
result += BORDER_CLASSES.fetch(@style) if bordered?
102+
result += ["inline-flex", "items-center"] if dot?
103+
result + @class
104+
end
105+
106+
def tag_name
107+
link? ? :a : :span
108+
end
109+
110+
def tag_options
111+
opts = {class: classes}
112+
opts[:href] = @href if link?
113+
opts.merge(options)
114+
end
115+
end
116+
end
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<%= content_tag(tag_name, **tag_options) do %>
2+
<% if dot? %><%= render(Flowbite::Badge::Dot.new(style: @style)) %><% end %>
3+
<%= content %>
4+
<% end %>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# frozen_string_literal: true
2+
3+
module Flowbite
4+
class Badge
5+
# Renders a colored dot indicator for use inside a badge.
6+
#
7+
# @param style [Symbol] The color style of the dot (:alternative, :brand,
8+
# :danger, :gray, :success, :warning).
9+
class Dot < ViewComponent::Base
10+
CLASSES = {
11+
alternative: ["bg-heading", "me-1", "rounded-full"],
12+
brand: ["bg-fg-brand-strong", "me-1", "rounded-full"],
13+
danger: ["bg-fg-danger-strong", "me-1", "rounded-full"],
14+
gray: ["bg-heading", "me-1", "rounded-full"],
15+
success: ["bg-fg-success-strong", "me-1", "rounded-full"],
16+
warning: ["bg-fg-warning", "me-1", "rounded-full"]
17+
}.freeze
18+
19+
SIZES = {
20+
default: ["h-1.5", "w-1.5"]
21+
}.freeze
22+
23+
class << self
24+
def classes(size: :default, style: :brand)
25+
CLASSES.fetch(style) + sizes.fetch(size)
26+
end
27+
28+
def sizes
29+
SIZES
30+
end
31+
end
32+
33+
attr_reader :size, :style
34+
35+
def initialize(size: :default, style: :brand)
36+
@size = size
37+
@style = style
38+
end
39+
40+
def call
41+
content_tag(:span, nil, class: self.class.classes(size: size, style: style))
42+
end
43+
end
44+
end
45+
end
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# frozen_string_literal: true
2+
3+
module Flowbite
4+
class Badge
5+
# Renders a pill-shaped badge with fully rounded corners.
6+
#
7+
# @example Basic usage
8+
# <%= render(Flowbite::Badge::Pill.new) { "Default" } %>
9+
#
10+
# @see https://flowbite.com/docs/components/badge/
11+
class Pill < Flowbite::Badge
12+
class << self
13+
# rubocop:disable Layout/LineLength
14+
def styles
15+
Flowbite::Styles.from_hash({
16+
alternative: {
17+
default: ["bg-neutral-primary-soft", "hover:bg-neutral-secondary-medium", "rounded-full", "text-heading"]
18+
},
19+
brand: {
20+
default: ["bg-brand-softer", "hover:bg-brand-soft", "rounded-full", "text-fg-brand-strong"]
21+
},
22+
danger: {
23+
default: ["bg-danger-soft", "hover:bg-danger-medium", "rounded-full", "text-fg-danger-strong"]
24+
},
25+
gray: {
26+
default: ["bg-neutral-secondary-medium", "hover:bg-neutral-tertiary-medium", "rounded-full", "text-heading"]
27+
},
28+
success: {
29+
default: ["bg-success-soft", "hover:bg-success-medium", "rounded-full", "text-fg-success-strong"]
30+
},
31+
warning: {
32+
default: ["bg-warning-soft", "hover:bg-warning-medium", "rounded-full", "text-fg-warning"]
33+
}
34+
}.freeze)
35+
end
36+
# rubocop:enable Layout/LineLength
37+
end
38+
end
39+
end
40+
end

demo/.yardoc/checksums

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
app/components/flowbite/card.rb 9fe54b52bc9d177c2ec1d9e68e0a397b8a327744
22
app/components/flowbite/link.rb 1516522405f7cf2021913a4ebbb792f4ae386c16
3+
app/components/flowbite/badge.rb e695813e46a6244924ca0707e6f788a1f59c4cee
34
app/components/flowbite/input.rb df2ae5f59a7d33a635599632386053f999f65919
45
app/components/flowbite/style.rb ef063360cc99cd7a6b8e67a7693326bb5dfb0e42
56
app/components/flowbite/toast.rb 6b822405dd55d87d56979e6cfba55e8f73965047
67
app/components/flowbite/button.rb 6ae7681d3b842d73aa99cddfa5a9b107ede7fea4
78
app/components/flowbite/styles.rb 929c42e428ba5a8e16efacaae0f35380e2f5f95c
89
app/components/flowbite/sidebar.rb 85033b602a098f3334b9b3e180239ef20a1b6f90
910
app/components/flowbite/input/url.rb f1046824f9b06c8df8e0f567979321b82baac6fa
11+
app/components/flowbite/badge/pill.rb cf713c5935e9300648f5501c90dced0aca488472
1012
app/components/flowbite/breadcrumb.rb c69ffb465b6e7f2489d4ac9a928e08bdf252fe99
1113
app/components/flowbite/card/title.rb 8067aa1e027c725896b063b67364aecfbf2f7d4e
1214
app/components/flowbite/input/date.rb 3b47f26b5622267e772c0d42d37655336ddf0169

demo/.yardoc/object_types

530 Bytes
Binary file not shown.

demo/.yardoc/objects/root.dat

10.7 KB
Binary file not shown.
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# frozen_string_literal: true
2+
3+
class BadgePreview < Lookbook::Preview
4+
def example
5+
render(Flowbite::Badge.new) { "Default" }
6+
end
7+
8+
# @!group Styles
9+
#
10+
# Use these badge styles with multiple colors to indicate status or categories.
11+
#
12+
# @display classes flex flex-wrap gap-2
13+
14+
def alternative
15+
render(Flowbite::Badge.new(style: :alternative)) { "Alternative" }
16+
end
17+
18+
def brand
19+
render(Flowbite::Badge.new(style: :brand)) { "Brand" }
20+
end
21+
22+
def danger
23+
render(Flowbite::Badge.new(style: :danger)) { "Danger" }
24+
end
25+
26+
def gray
27+
render(Flowbite::Badge.new(style: :gray)) { "Gray" }
28+
end
29+
30+
def success
31+
render(Flowbite::Badge.new(style: :success)) { "Success" }
32+
end
33+
34+
def warning
35+
render(Flowbite::Badge.new(style: :warning)) { "Warning" }
36+
end
37+
38+
# @!endgroup
39+
40+
# @!group Bordered
41+
#
42+
# Add a border accent in a matching color scheme.
43+
#
44+
# @display classes flex flex-wrap gap-2
45+
46+
def bordered_alternative
47+
render(Flowbite::Badge.new(bordered: true, style: :alternative)) { "Alternative" }
48+
end
49+
50+
def bordered_brand
51+
render(Flowbite::Badge.new(bordered: true, style: :brand)) { "Brand" }
52+
end
53+
54+
def bordered_danger
55+
render(Flowbite::Badge.new(bordered: true, style: :danger)) { "Danger" }
56+
end
57+
58+
def bordered_gray
59+
render(Flowbite::Badge.new(bordered: true, style: :gray)) { "Gray" }
60+
end
61+
62+
def bordered_success
63+
render(Flowbite::Badge.new(bordered: true, style: :success)) { "Success" }
64+
end
65+
66+
def bordered_warning
67+
render(Flowbite::Badge.new(bordered: true, style: :warning)) { "Warning" }
68+
end
69+
70+
# @!endgroup
71+
72+
# @!group Large
73+
#
74+
# Increase the paddings to create a larger badge variant.
75+
#
76+
# @display classes flex flex-wrap gap-2
77+
78+
def large_brand
79+
render(Flowbite::Badge.new(size: :lg, style: :brand)) { "Brand" }
80+
end
81+
82+
def large_bordered
83+
render(Flowbite::Badge.new(bordered: true, size: :lg, style: :brand)) { "Brand" }
84+
end
85+
86+
# @!endgroup
87+
88+
# @!group Pill
89+
#
90+
# Make the corners even more rounded like pills.
91+
#
92+
# @display classes flex flex-wrap gap-2
93+
94+
def pill_alternative
95+
render(Flowbite::Badge::Pill.new(style: :alternative)) { "Alternative" }
96+
end
97+
98+
def pill_brand
99+
render(Flowbite::Badge::Pill.new(style: :brand)) { "Brand" }
100+
end
101+
102+
def pill_danger
103+
render(Flowbite::Badge::Pill.new(style: :danger)) { "Danger" }
104+
end
105+
106+
def pill_gray
107+
render(Flowbite::Badge::Pill.new(style: :gray)) { "Gray" }
108+
end
109+
110+
def pill_success
111+
render(Flowbite::Badge::Pill.new(style: :success)) { "Success" }
112+
end
113+
114+
def pill_warning
115+
render(Flowbite::Badge::Pill.new(style: :warning)) { "Warning" }
116+
end
117+
118+
# @!endgroup
119+
120+
# @!group Link
121+
#
122+
# Use badges as anchor elements to link to another page.
123+
#
124+
# @display classes flex flex-wrap gap-2
125+
126+
def link_badge
127+
render(Flowbite::Badge.new(bordered: true, href: "#", style: :brand)) { "Brand" }
128+
end
129+
130+
def link_pill
131+
render(Flowbite::Badge::Pill.new(bordered: true, href: "#", style: :brand)) { "Brand" }
132+
end
133+
134+
# @!endgroup
135+
136+
# @!group Dot
137+
#
138+
# Add a colored dot indicator before the badge text.
139+
#
140+
# @display classes flex flex-wrap gap-2
141+
142+
def dot_alternative
143+
render(Flowbite::Badge.new(bordered: true, dot: true, style: :alternative)) { "Alternative" }
144+
end
145+
146+
def dot_brand
147+
render(Flowbite::Badge.new(bordered: true, dot: true, style: :brand)) { "Brand" }
148+
end
149+
150+
def dot_danger
151+
render(Flowbite::Badge.new(bordered: true, dot: true, style: :danger)) { "Danger" }
152+
end
153+
154+
def dot_gray
155+
render(Flowbite::Badge.new(bordered: true, dot: true, style: :gray)) { "Gray" }
156+
end
157+
158+
def dot_success
159+
render(Flowbite::Badge.new(bordered: true, dot: true, style: :success)) { "Success" }
160+
end
161+
162+
def dot_warning
163+
render(Flowbite::Badge.new(bordered: true, dot: true, style: :warning)) { "Warning" }
164+
end
165+
166+
# @!endgroup
167+
end

0 commit comments

Comments
 (0)