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 @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added

* Badge component.
* Sidebar component.

### Changed
Expand Down
116 changes: 116 additions & 0 deletions app/components/flowbite/badge.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# frozen_string_literal: true

module Flowbite
# Renders a badge component for displaying labels, counts, or status
# indicators.
#
# @example Basic usage
# <%= render(Flowbite::Badge.new) { "Default" } %>
#
# @example With border
# <%= render(Flowbite::Badge.new(bordered: true, style: :success)) { "Success" } %>
#
# @see https://flowbite.com/docs/components/badge/
# @lookbook_embed BadgePreview
class Badge < ViewComponent::Base
SIZES = {
default: ["text-xs", "font-medium", "px-1.5", "py-0.5"],
lg: ["text-sm", "font-medium", "px-2", "py-1"]
}.freeze

BORDER_CLASSES = {
alternative: ["border", "border-default"],
brand: ["border", "border-brand-subtle"],
danger: ["border", "border-danger-subtle"],
gray: ["border", "border-default-medium"],
success: ["border", "border-success-subtle"],
warning: ["border", "border-warning-subtle"]
}.freeze

class << self
def classes(size: :default, state: :default, style: :brand)
styles.fetch(style).fetch(state) + sizes.fetch(size)
end

def sizes
SIZES
end
Comment on lines +30 to +37
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Badge class is missing a sizes class method that returns the SIZES constant, which is a pattern used in other components like Button and Input. This method provides a way to access sizes from the current class rather than a parent class. Consider adding def sizes; SIZES; end inside the class << self block for consistency with other components.

Copilot uses AI. Check for mistakes.

# rubocop:disable Layout/LineLength
def styles
Flowbite::Styles.from_hash({
alternative: {
default: ["bg-neutral-primary-soft", "hover:bg-neutral-secondary-medium", "rounded", "text-heading"]
},
brand: {
default: ["bg-brand-softer", "hover:bg-brand-soft", "rounded", "text-fg-brand-strong"]
},
danger: {
default: ["bg-danger-soft", "hover:bg-danger-medium", "rounded", "text-fg-danger-strong"]
},
gray: {
default: ["bg-neutral-secondary-medium", "hover:bg-neutral-tertiary-medium", "rounded", "text-heading"]
},
success: {
default: ["bg-success-soft", "hover:bg-success-medium", "rounded", "text-fg-success-strong"]
},
warning: {
default: ["bg-warning-soft", "hover:bg-warning-medium", "rounded", "text-fg-warning"]
}
}.freeze)
end
# rubocop:enable Layout/LineLength
end

attr_reader :options

# @param bordered [Boolean] Whether to add a border to the badge.
# @param class [String, Array<String>] Additional CSS classes.
# @param dot [Boolean] Whether to show a dot indicator.
# @param href [String] If provided, renders the badge as a link.
# @param size [Symbol] The size of the badge (:default or :lg).
# @param style [Symbol] The color style (:alternative, :brand, :danger,
# :gray, :success, :warning).
def initialize(bordered: false, class: nil, dot: false, href: nil,
size: :default, style: :brand, **options)
@bordered = bordered
@class = Array.wrap(binding.local_variable_get(:class))
@dot = dot
@href = href
@size = size
@style = style
@options = options
end

def bordered?
!!@bordered
end

def dot?
!!@dot
end

def link?
@href.present?
end

private

def classes
result = self.class.classes(size: @size, state: :default, style: @style)
result += BORDER_CLASSES.fetch(@style) if bordered?
result += ["inline-flex", "items-center"] if dot?
result + @class
end

def tag_name
link? ? :a : :span
end

def tag_options
opts = {class: classes}
opts[:href] = @href if link?
opts.merge(options)
end
end
end
4 changes: 4 additions & 0 deletions app/components/flowbite/badge/badge.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<%= content_tag(tag_name, **tag_options) do %>
<% if dot? %><%= render(Flowbite::Badge::Dot.new(style: @style)) %><% end %>
<%= content %>
<% end %>
45 changes: 45 additions & 0 deletions app/components/flowbite/badge/dot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

module Flowbite
class Badge
# Renders a colored dot indicator for use inside a badge.
#
# @param style [Symbol] The color style of the dot (:alternative, :brand,
# :danger, :gray, :success, :warning).
class Dot < ViewComponent::Base
CLASSES = {
alternative: ["bg-heading", "me-1", "rounded-full"],
brand: ["bg-fg-brand-strong", "me-1", "rounded-full"],
danger: ["bg-fg-danger-strong", "me-1", "rounded-full"],
gray: ["bg-heading", "me-1", "rounded-full"],
success: ["bg-fg-success-strong", "me-1", "rounded-full"],
warning: ["bg-fg-warning", "me-1", "rounded-full"]
}.freeze

SIZES = {
default: ["h-1.5", "w-1.5"]
}.freeze

class << self
def classes(size: :default, style: :brand)
CLASSES.fetch(style) + sizes.fetch(size)
end

def sizes
SIZES
end
end

attr_reader :size, :style

def initialize(size: :default, style: :brand)
@size = size
@style = style
end

def call
content_tag(:span, nil, class: self.class.classes(size: size, style: style))
end
end
end
end
40 changes: 40 additions & 0 deletions app/components/flowbite/badge/pill.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

module Flowbite
class Badge
# Renders a pill-shaped badge with fully rounded corners.
#
# @example Basic usage
# <%= render(Flowbite::Badge::Pill.new) { "Default" } %>
#
# @see https://flowbite.com/docs/components/badge/
class Pill < Flowbite::Badge
class << self
# rubocop:disable Layout/LineLength
def styles
Flowbite::Styles.from_hash({
alternative: {
default: ["bg-neutral-primary-soft", "hover:bg-neutral-secondary-medium", "rounded-full", "text-heading"]
},
brand: {
default: ["bg-brand-softer", "hover:bg-brand-soft", "rounded-full", "text-fg-brand-strong"]
},
danger: {
default: ["bg-danger-soft", "hover:bg-danger-medium", "rounded-full", "text-fg-danger-strong"]
},
gray: {
default: ["bg-neutral-secondary-medium", "hover:bg-neutral-tertiary-medium", "rounded-full", "text-heading"]
},
success: {
default: ["bg-success-soft", "hover:bg-success-medium", "rounded-full", "text-fg-success-strong"]
},
warning: {
default: ["bg-warning-soft", "hover:bg-warning-medium", "rounded-full", "text-fg-warning"]
}
}.freeze)
end
# rubocop:enable Layout/LineLength
end
end
end
end
2 changes: 2 additions & 0 deletions demo/.yardoc/checksums
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
app/components/flowbite/card.rb 9fe54b52bc9d177c2ec1d9e68e0a397b8a327744
app/components/flowbite/link.rb 1516522405f7cf2021913a4ebbb792f4ae386c16
app/components/flowbite/badge.rb e695813e46a6244924ca0707e6f788a1f59c4cee
app/components/flowbite/input.rb df2ae5f59a7d33a635599632386053f999f65919
app/components/flowbite/style.rb ef063360cc99cd7a6b8e67a7693326bb5dfb0e42
app/components/flowbite/toast.rb 6b822405dd55d87d56979e6cfba55e8f73965047
app/components/flowbite/button.rb 6ae7681d3b842d73aa99cddfa5a9b107ede7fea4
app/components/flowbite/styles.rb 929c42e428ba5a8e16efacaae0f35380e2f5f95c
app/components/flowbite/sidebar.rb 85033b602a098f3334b9b3e180239ef20a1b6f90
app/components/flowbite/input/url.rb f1046824f9b06c8df8e0f567979321b82baac6fa
app/components/flowbite/badge/pill.rb cf713c5935e9300648f5501c90dced0aca488472
app/components/flowbite/breadcrumb.rb c69ffb465b6e7f2489d4ac9a928e08bdf252fe99
app/components/flowbite/card/title.rb 8067aa1e027c725896b063b67364aecfbf2f7d4e
app/components/flowbite/input/date.rb 3b47f26b5622267e772c0d42d37655336ddf0169
Expand Down
Binary file modified demo/.yardoc/object_types
Binary file not shown.
Binary file modified demo/.yardoc/objects/root.dat
Binary file not shown.
167 changes: 167 additions & 0 deletions demo/test/components/previews/badge_preview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# frozen_string_literal: true

class BadgePreview < Lookbook::Preview
def example
render(Flowbite::Badge.new) { "Default" }
end

# @!group Styles
#
# Use these badge styles with multiple colors to indicate status or categories.
#
# @display classes flex flex-wrap gap-2

def alternative
render(Flowbite::Badge.new(style: :alternative)) { "Alternative" }
end

def brand
render(Flowbite::Badge.new(style: :brand)) { "Brand" }
end

def danger
render(Flowbite::Badge.new(style: :danger)) { "Danger" }
end

def gray
render(Flowbite::Badge.new(style: :gray)) { "Gray" }
end

def success
render(Flowbite::Badge.new(style: :success)) { "Success" }
end

def warning
render(Flowbite::Badge.new(style: :warning)) { "Warning" }
end

# @!endgroup

# @!group Bordered
#
# Add a border accent in a matching color scheme.
#
# @display classes flex flex-wrap gap-2

def bordered_alternative
render(Flowbite::Badge.new(bordered: true, style: :alternative)) { "Alternative" }
end

def bordered_brand
render(Flowbite::Badge.new(bordered: true, style: :brand)) { "Brand" }
end

def bordered_danger
render(Flowbite::Badge.new(bordered: true, style: :danger)) { "Danger" }
end

def bordered_gray
render(Flowbite::Badge.new(bordered: true, style: :gray)) { "Gray" }
end

def bordered_success
render(Flowbite::Badge.new(bordered: true, style: :success)) { "Success" }
end

def bordered_warning
render(Flowbite::Badge.new(bordered: true, style: :warning)) { "Warning" }
end

# @!endgroup

# @!group Large
#
# Increase the paddings to create a larger badge variant.
#
# @display classes flex flex-wrap gap-2

def large_brand
render(Flowbite::Badge.new(size: :lg, style: :brand)) { "Brand" }
end

def large_bordered
render(Flowbite::Badge.new(bordered: true, size: :lg, style: :brand)) { "Brand" }
end

# @!endgroup

# @!group Pill
#
# Make the corners even more rounded like pills.
#
# @display classes flex flex-wrap gap-2

def pill_alternative
render(Flowbite::Badge::Pill.new(style: :alternative)) { "Alternative" }
end

def pill_brand
render(Flowbite::Badge::Pill.new(style: :brand)) { "Brand" }
end

def pill_danger
render(Flowbite::Badge::Pill.new(style: :danger)) { "Danger" }
end

def pill_gray
render(Flowbite::Badge::Pill.new(style: :gray)) { "Gray" }
end

def pill_success
render(Flowbite::Badge::Pill.new(style: :success)) { "Success" }
end

def pill_warning
render(Flowbite::Badge::Pill.new(style: :warning)) { "Warning" }
end

# @!endgroup

# @!group Link
#
# Use badges as anchor elements to link to another page.
#
# @display classes flex flex-wrap gap-2

def link_badge
render(Flowbite::Badge.new(bordered: true, href: "#", style: :brand)) { "Brand" }
end

def link_pill
render(Flowbite::Badge::Pill.new(bordered: true, href: "#", style: :brand)) { "Brand" }
end

# @!endgroup

# @!group Dot
#
# Add a colored dot indicator before the badge text.
#
# @display classes flex flex-wrap gap-2

def dot_alternative
render(Flowbite::Badge.new(bordered: true, dot: true, style: :alternative)) { "Alternative" }
end

def dot_brand
render(Flowbite::Badge.new(bordered: true, dot: true, style: :brand)) { "Brand" }
end

def dot_danger
render(Flowbite::Badge.new(bordered: true, dot: true, style: :danger)) { "Danger" }
end

def dot_gray
render(Flowbite::Badge.new(bordered: true, dot: true, style: :gray)) { "Gray" }
end

def dot_success
render(Flowbite::Badge.new(bordered: true, dot: true, style: :success)) { "Success" }
end

def dot_warning
render(Flowbite::Badge.new(bordered: true, dot: true, style: :warning)) { "Warning" }
end

# @!endgroup
end
Loading