diff --git a/lib/phlexy_ui/indicator.rb b/lib/phlexy_ui/indicator.rb
new file mode 100644
index 0000000..53c8b08
--- /dev/null
+++ b/lib/phlexy_ui/indicator.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module PhlexyUI
+ class Indicator < Base
+ def initialize(*, as: :div, **)
+ super(*, **)
+ @as = as
+ end
+
+ def view_template(&)
+ generate_classes!(
+ # "indicator"
+ component_html_class: :indicator,
+ options:
+ ).then do |classes|
+ public_send(as, class: classes, **options, &)
+ end
+ end
+
+ def item(*base_modifiers, **, &)
+ render IndicatorItem.new(*base_modifiers, **, &)
+ end
+ end
+end
diff --git a/lib/phlexy_ui/indicator_item.rb b/lib/phlexy_ui/indicator_item.rb
new file mode 100644
index 0000000..c162965
--- /dev/null
+++ b/lib/phlexy_ui/indicator_item.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module PhlexyUI
+ class IndicatorItem < Base
+ def initialize(*, as: :span, **)
+ super(*, **)
+ @as = as
+ end
+
+ def view_template(&)
+ generate_classes!(
+ # "indicator-item"
+ component_html_class: :"indicator-item",
+ modifiers_map: modifiers,
+ base_modifiers:,
+ options:
+ ).then do |classes|
+ public_send(as, class: classes, **options, &)
+ end
+ end
+
+ private
+
+ register_modifiers(
+ # "sm:indicator-start"
+ # "@sm:indicator-start"
+ # "md:indicator-start"
+ # "@md:indicator-start"
+ # "lg:indicator-start"
+ # "@lg:indicator-start"
+ start: "indicator-start",
+ # "sm:indicator-center"
+ # "@sm:indicator-center"
+ # "md:indicator-center"
+ # "@md:indicator-center"
+ # "lg:indicator-center"
+ # "@lg:indicator-center"
+ center: "indicator-center",
+ # "sm:indicator-end"
+ # "@sm:indicator-end"
+ # "md:indicator-end"
+ # "@md:indicator-end"
+ # "lg:indicator-end"
+ # "@lg:indicator-end"
+ end: "indicator-end",
+ # "sm:indicator-top"
+ # "@sm:indicator-top"
+ # "md:indicator-top"
+ # "@md:indicator-top"
+ # "lg:indicator-top"
+ # "@lg:indicator-top"
+ top: "indicator-top",
+ # "sm:indicator-middle"
+ # "@sm:indicator-middle"
+ # "md:indicator-middle"
+ # "@md:indicator-middle"
+ # "lg:indicator-middle"
+ # "@lg:indicator-middle"
+ middle: "indicator-middle",
+ # "sm:indicator-bottom"
+ # "@sm:indicator-bottom"
+ # "md:indicator-bottom"
+ # "@md:indicator-bottom"
+ # "lg:indicator-bottom"
+ # "@lg:indicator-bottom"
+ bottom: "indicator-bottom"
+ )
+ end
+end
diff --git a/spec/lib/phlexy_ui/indicator_spec.rb b/spec/lib/phlexy_ui/indicator_spec.rb
new file mode 100644
index 0000000..d49caac
--- /dev/null
+++ b/spec/lib/phlexy_ui/indicator_spec.rb
@@ -0,0 +1,199 @@
+require "spec_helper"
+
+describe PhlexyUI::Indicator do
+ subject(:output) { render described_class.new }
+
+ it "is expected to match the formatted HTML" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ is_expected.to eq(expected_html)
+ end
+
+ describe "with item" do
+ subject(:output) do
+ render described_class.new do |i|
+ i.item { "Badge 1" }
+ i.item { "Badge 2" }
+ end
+ end
+
+ it "renders item" do
+ expected_html = html <<~HTML
+
+ Badge 1
+ Badge 2
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+
+ describe "conditions" do
+ {
+ start: "indicator-start",
+ center: "indicator-center",
+ end: "indicator-end",
+ top: "indicator-top",
+ middle: "indicator-middle",
+ bottom: "indicator-bottom"
+ }.each do |modifier, css|
+ context "when given :#{modifier} modifier on an item" do
+ subject(:output) do
+ render described_class.new do |indicator|
+ indicator.item modifier
+ end
+ end
+
+ it "renders it apart from the main class" do
+ expected_html = html <<~HTML
+
+
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+ end
+
+ context "when given multiple conditions on an item" do
+ subject(:output) do
+ render described_class.new do |indicator|
+ indicator.item :top, :end
+ end
+ end
+
+ it "renders them separately" do
+ expected_html = html <<~HTML
+
+
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+ end
+
+ describe "data" do
+ subject(:output) do
+ render described_class.new(data: {foo: "bar"}) do |indicator|
+ indicator.item data: {baz: "qux"}
+ end
+ end
+
+ it "renders it correctly" do
+ expected_html = html <<~HTML
+
+
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+
+ describe "responsiveness" do
+ %i[sm md lg xl @sm @md @lg @xl].each do |viewport|
+ context "when given an :#{viewport} responsive option" do
+ subject(:output) do
+ render described_class.new do |indicator|
+ indicator.item responsive: {viewport => :bottom}
+ indicator.item responsive: {viewport => true}
+ end
+ end
+
+ it "renders it separately with a responsive prefix" do
+ expected_html = html <<~HTML
+
+
+
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+ end
+ end
+
+ describe "passing :as option" do
+ subject(:output) { render described_class.new(as: :div) }
+
+ it "renders as the given tag" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+
+ describe "rendering via Kit" do
+ subject(:output) do
+ Indicator do |indicator|
+ indicator.item :top
+ end
+ end
+
+ it "renders it correctly" do
+ expected_html = html <<~HTML
+
+
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+
+ describe "rendering a full indicator" do
+ let(:component) do
+ Class.new(Phlex::HTML) do
+ def view_template(&)
+ # Ignores :top modifier on the main component
+ render PhlexyUI::Indicator.new(:top, data: {foo: "bar"}) do |i|
+ i.item(:top, :start, class: "badge", data: {baz: "qux"}) { "↖︎" }
+ i.item(:top, :center, class: "badge") { "↑" }
+ i.item(:top, :end, class: "badge") { "↗︎" }
+ i.item(:middle, :start, class: "badge") { "←" }
+ i.item(:middle, :center, class: "badge") { "●" }
+ i.item(:middle, :end, class: "badge") { "→" }
+ i.item(:bottom, :start, class: "badge") { "↙︎" }
+ i.item(:bottom, :center, class: "badge") { "↓" }
+ i.item(:bottom, :end, class: "badge") { "↘︎" }
+ div(class: "bg-base-300 grid h-32 w-60 place-items-center") do
+ "Box"
+ end
+ end
+ end
+ end
+ end
+
+ subject(:output) do
+ render component.new
+ end
+
+ it "renders it correctly" do
+ expected_html = html <<~HTML
+
+
↖︎
+
↑
+
↗︎
+
←
+
●
+
→
+
↙︎
+
↓
+
↘︎
+
Box
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+end