From 7562f0fedc9772d3efbd6d874ff8b7a723c66329 Mon Sep 17 00:00:00 2001 From: Jakob Skjerning Date: Sun, 15 Feb 2026 19:54:32 +0100 Subject: [PATCH 1/5] Add Badge component https://flowbite.com/docs/components/badge/ --- app/components/flowbite/badge.rb | 125 ++++++++++++ app/components/flowbite/badge/badge.html.erb | 4 + app/components/flowbite/badge/pill.rb | 40 ++++ demo/.yardoc/checksums | 2 + demo/.yardoc/object_types | Bin 10176 -> 10706 bytes demo/.yardoc/objects/root.dat | Bin 145737 -> 156652 bytes .../test/components/previews/badge_preview.rb | 167 +++++++++++++++ test/components/flowbite/badge_test.rb | 193 ++++++++++++++++++ 8 files changed, 531 insertions(+) create mode 100644 app/components/flowbite/badge.rb create mode 100644 app/components/flowbite/badge/badge.html.erb create mode 100644 app/components/flowbite/badge/pill.rb create mode 100644 demo/test/components/previews/badge_preview.rb create mode 100644 test/components/flowbite/badge_test.rb diff --git a/app/components/flowbite/badge.rb b/app/components/flowbite/badge.rb new file mode 100644 index 0000000..a5249c3 --- /dev/null +++ b/app/components/flowbite/badge.rb @@ -0,0 +1,125 @@ +# 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 + + DOT_CLASSES = { + alternative: ["h-1.5", "w-1.5", "bg-heading", "me-1", "rounded-full"], + brand: ["h-1.5", "w-1.5", "bg-fg-brand-strong", "me-1", "rounded-full"], + danger: ["h-1.5", "w-1.5", "bg-fg-danger-strong", "me-1", "rounded-full"], + gray: ["h-1.5", "w-1.5", "bg-heading", "me-1", "rounded-full"], + success: ["h-1.5", "w-1.5", "bg-fg-success-strong", "me-1", "rounded-full"], + warning: ["h-1.5", "w-1.5", "bg-fg-warning", "me-1", "rounded-full"] + }.freeze + + class << self + # 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] 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.styles.fetch(@style).fetch(:default) + size_classes + result += BORDER_CLASSES.fetch(@style) if bordered? + result += ["inline-flex", "items-center"] if dot? + result + @class + end + + def dot_classes + DOT_CLASSES.fetch(@style) + end + + def size_classes + SIZES.fetch(@size) + 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 diff --git a/app/components/flowbite/badge/badge.html.erb b/app/components/flowbite/badge/badge.html.erb new file mode 100644 index 0000000..52ac6fd --- /dev/null +++ b/app/components/flowbite/badge/badge.html.erb @@ -0,0 +1,4 @@ +<%= content_tag(tag_name, **tag_options) do %> + <% if dot? %>"><% end %> + <%= content %> +<% end %> diff --git a/app/components/flowbite/badge/pill.rb b/app/components/flowbite/badge/pill.rb new file mode 100644 index 0000000..ed0f845 --- /dev/null +++ b/app/components/flowbite/badge/pill.rb @@ -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 diff --git a/demo/.yardoc/checksums b/demo/.yardoc/checksums index a5792e2..24d3072 100644 --- a/demo/.yardoc/checksums +++ b/demo/.yardoc/checksums @@ -1,5 +1,6 @@ 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 @@ -7,6 +8,7 @@ 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 diff --git a/demo/.yardoc/object_types b/demo/.yardoc/object_types index a5bb2e9de0b2f2868bf711fb8808b6621db0322b..0c3b1877908c3fe94de2a59964de461b0414c3f8 100644 GIT binary patch delta 483 zcmX@$e<^r^0i)$aLvvQA#FX^ZiQ!jdrQCAz%abxoQmw4OVpdiGnK?OZ*6ePcN|KW= zvWRSUV3c8Hys|l#Z7<{Gl^onkU}bv6C6zg;#SnEeXl6|2=aGciFu9jUCI+lQIVry= zCABCu#U5%lnjYnp{1T`Xx|5W1GV`*bau`2$|Z^E@p*~4sSw*0&}|1v=NFV@=I0epPUIBdtj$r(s-lRd3KBLbUY7$0(PrLF z67_LgCTaRU`RUo2f4Tg#XI{r1_#4bVcO~9AA+g`$rjJNg3=NzE08Ul I>(rbW0eW?sZ2$lO delta 61 zcmV-D0K)&$Q@~Gr^aseC#0m!qD2E753X$T9mt_gVslh6ta TlWQdkvtJLg7y%Bmcqm!{q{I}N diff --git a/demo/.yardoc/objects/root.dat b/demo/.yardoc/objects/root.dat index a52ce9c023a5a6c6798ac35e7afe87ed4761a2d5..0220623dad0cb7f50c404ca6ab41b98def90839f 100644 GIT binary patch delta 22518 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 Date: Sun, 15 Feb 2026 19:55:11 +0100 Subject: [PATCH 2/5] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3946e73..db08d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added +* Badge component. * Sidebar component. ### Changed From 2542ee496191d72303438e2066a23b027c58ecbb Mon Sep 17 00:00:00 2001 From: Jakob Skjerning Date: Mon, 16 Feb 2026 16:21:36 +0100 Subject: [PATCH 3/5] Add Flowbite::Badge::Dot component --- app/components/flowbite/badge.rb | 13 --------- app/components/flowbite/badge/badge.html.erb | 2 +- app/components/flowbite/badge/dot.rb | 30 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 app/components/flowbite/badge/dot.rb diff --git a/app/components/flowbite/badge.rb b/app/components/flowbite/badge.rb index a5249c3..49c1930 100644 --- a/app/components/flowbite/badge.rb +++ b/app/components/flowbite/badge.rb @@ -27,15 +27,6 @@ class Badge < ViewComponent::Base warning: ["border", "border-warning-subtle"] }.freeze - DOT_CLASSES = { - alternative: ["h-1.5", "w-1.5", "bg-heading", "me-1", "rounded-full"], - brand: ["h-1.5", "w-1.5", "bg-fg-brand-strong", "me-1", "rounded-full"], - danger: ["h-1.5", "w-1.5", "bg-fg-danger-strong", "me-1", "rounded-full"], - gray: ["h-1.5", "w-1.5", "bg-heading", "me-1", "rounded-full"], - success: ["h-1.5", "w-1.5", "bg-fg-success-strong", "me-1", "rounded-full"], - warning: ["h-1.5", "w-1.5", "bg-fg-warning", "me-1", "rounded-full"] - }.freeze - class << self # rubocop:disable Layout/LineLength def styles @@ -104,10 +95,6 @@ def classes result + @class end - def dot_classes - DOT_CLASSES.fetch(@style) - end - def size_classes SIZES.fetch(@size) end diff --git a/app/components/flowbite/badge/badge.html.erb b/app/components/flowbite/badge/badge.html.erb index 52ac6fd..0ad88b2 100644 --- a/app/components/flowbite/badge/badge.html.erb +++ b/app/components/flowbite/badge/badge.html.erb @@ -1,4 +1,4 @@ <%= content_tag(tag_name, **tag_options) do %> - <% if dot? %>"><% end %> + <% if dot? %><%= render(Flowbite::Badge::Dot.new(style: @style)) %><% end %> <%= content %> <% end %> diff --git a/app/components/flowbite/badge/dot.rb b/app/components/flowbite/badge/dot.rb new file mode 100644 index 0000000..8639c79 --- /dev/null +++ b/app/components/flowbite/badge/dot.rb @@ -0,0 +1,30 @@ +# 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", "h-1.5", "me-1", "rounded-full", "w-1.5"], + brand: ["bg-fg-brand-strong", "h-1.5", "me-1", "rounded-full", "w-1.5"], + danger: ["bg-fg-danger-strong", "h-1.5", "me-1", "rounded-full", "w-1.5"], + gray: ["bg-heading", "h-1.5", "me-1", "rounded-full", "w-1.5"], + success: ["bg-fg-success-strong", "h-1.5", "me-1", "rounded-full", "w-1.5"], + warning: ["bg-fg-warning", "h-1.5", "me-1", "rounded-full", "w-1.5"] + }.freeze + + attr_reader :style + + def initialize(style: :brand) + @style = style + end + + def call + content_tag(:span, nil, class: CLASSES.fetch(style)) + end + end + end +end From 0d61de58b6e5c901f48e2eadf6a5704a5aa94882 Mon Sep 17 00:00:00 2001 From: Jakob Skjerning Date: Mon, 16 Feb 2026 16:24:28 +0100 Subject: [PATCH 4/5] Add classes class methods --- app/components/flowbite/badge.rb | 14 +++++++++----- app/components/flowbite/badge/dot.rb | 8 +++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/components/flowbite/badge.rb b/app/components/flowbite/badge.rb index 49c1930..3c5ea4b 100644 --- a/app/components/flowbite/badge.rb +++ b/app/components/flowbite/badge.rb @@ -28,6 +28,14 @@ class Badge < ViewComponent::Base }.freeze class << self + def classes(size: :default, state: :default, style: :brand) + styles.fetch(style).fetch(state) + sizes.fetch(size) + end + + def sizes + SIZES + end + # rubocop:disable Layout/LineLength def styles Flowbite::Styles.from_hash({ @@ -89,16 +97,12 @@ def link? private def classes - result = self.class.styles.fetch(@style).fetch(:default) + size_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 size_classes - SIZES.fetch(@size) - end - def tag_name link? ? :a : :span end diff --git a/app/components/flowbite/badge/dot.rb b/app/components/flowbite/badge/dot.rb index 8639c79..dfce531 100644 --- a/app/components/flowbite/badge/dot.rb +++ b/app/components/flowbite/badge/dot.rb @@ -16,6 +16,12 @@ class Dot < ViewComponent::Base warning: ["bg-fg-warning", "h-1.5", "me-1", "rounded-full", "w-1.5"] }.freeze + class << self + def classes(style: :brand) + CLASSES.fetch(style) + end + end + attr_reader :style def initialize(style: :brand) @@ -23,7 +29,7 @@ def initialize(style: :brand) end def call - content_tag(:span, nil, class: CLASSES.fetch(style)) + content_tag(:span, nil, class: self.class.classes(style: style)) end end end From 5ddc5af3a64e4b09c7a8f5aa5dd9c196b89f0caa Mon Sep 17 00:00:00 2001 From: Jakob Skjerning Date: Wed, 18 Feb 2026 15:22:35 +0100 Subject: [PATCH 5/5] Make size configurable in Dot component Primarily to extract the size class names --- app/components/flowbite/badge/dot.rb | 31 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/app/components/flowbite/badge/dot.rb b/app/components/flowbite/badge/dot.rb index dfce531..d8fc567 100644 --- a/app/components/flowbite/badge/dot.rb +++ b/app/components/flowbite/badge/dot.rb @@ -8,28 +8,37 @@ class Badge # :danger, :gray, :success, :warning). class Dot < ViewComponent::Base CLASSES = { - alternative: ["bg-heading", "h-1.5", "me-1", "rounded-full", "w-1.5"], - brand: ["bg-fg-brand-strong", "h-1.5", "me-1", "rounded-full", "w-1.5"], - danger: ["bg-fg-danger-strong", "h-1.5", "me-1", "rounded-full", "w-1.5"], - gray: ["bg-heading", "h-1.5", "me-1", "rounded-full", "w-1.5"], - success: ["bg-fg-success-strong", "h-1.5", "me-1", "rounded-full", "w-1.5"], - warning: ["bg-fg-warning", "h-1.5", "me-1", "rounded-full", "w-1.5"] + 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(style: :brand) - CLASSES.fetch(style) + def classes(size: :default, style: :brand) + CLASSES.fetch(style) + sizes.fetch(size) + end + + def sizes + SIZES end end - attr_reader :style + attr_reader :size, :style - def initialize(style: :brand) + def initialize(size: :default, style: :brand) + @size = size @style = style end def call - content_tag(:span, nil, class: self.class.classes(style: style)) + content_tag(:span, nil, class: self.class.classes(size: size, style: style)) end end end