mirror of
https://github.com/we-promise/sure.git
synced 2026-05-27 22:44:55 +00:00
Migrates the hand-rolled "Pending" / "Review recommended" / "Potential duplicate" / "Split" badges across the transaction views to the extended DS::Pill primitive from #1902. **Visual contract for badge mode** In #1902 the badge mode (`marker: false`) used `rounded-md` (chip shape) because the marker mode does. But every existing pill / status badge in the codebase uses `rounded-full` — see `settings/providers/_status_pill.html.erb`, `settings/providers/_maturity_badge.html.erb`, and the inline transaction badges this PR is migrating. To keep the visual contract consistent, this PR shifts `DS::Pill`'s badge mode to `rounded-full` (marker mode stays `rounded-md`, unchanged from #1829). The shape distinction now reads: markers are tags, badges are pills. **Callsites migrated** (5): - `app/views/transactions/_transaction.html.erb` — Pending, Review-recommended, Possible-duplicate, Split badges - `app/views/transactions/_header.html.erb` — Pending badge - `app/views/transactions/_split_parent_row.html.erb` — Split badge **Tone mapping** | Badge | Tone | Notes | |---|---|---| | Pending | `:neutral` | unchanged copy/icon, gains subtle DS-controlled bg | | Review recommended | `:neutral` | matches existing `bg-surface-inset` look | | Possible duplicate | `:warning` | DS semantic alias for the existing `text-warning` | | Split | `:neutral` | matches existing `bg-surface-inset` look | **Deferred to follow-up PRs** - `app/views/transactions/_transfer_match.html.erb` — uses two responsive-visibility variants (`hidden lg:inline-flex` for long copy, `inline-flex lg:hidden` for short). DS::Pill currently has no `class:` arg for caller-controlled wrapper classes; deferring until that lands. - `app/views/transactions/searches/filters/_badge.html.erb` — has a close button alongside the label (`button_to clear_filter_*`) and uses `rounded-3xl p-1.5` instead of a true pill. Closer to a removable filter chip — better fit for a separate `DS::FilterChip` primitive than for `DS::Pill`. Refs #1751.
This commit is contained in:
committed by
GitHub
parent
814505c5ea
commit
20844923e6
@@ -104,24 +104,26 @@ class DS::Pill < DesignSystemComponent
|
||||
def container_classes
|
||||
base = [
|
||||
"inline-flex items-center align-middle font-medium whitespace-nowrap shrink-0",
|
||||
"border rounded-md",
|
||||
"leading-none"
|
||||
"border leading-none"
|
||||
]
|
||||
|
||||
if marker
|
||||
# Marker mode (Beta / Canary / NEW): uppercase, sub-12px text,
|
||||
# wider tracking. text-[10/11px] stays as arbitrary values — the
|
||||
# pill is intentionally sub-12px (Sure's smallest scale token is
|
||||
# text-xs / 12px) so it reads as a marker, not a label. Padding /
|
||||
# gap / tracking snap to Tailwind's scale to satisfy the
|
||||
# design-system "no arbitrary values" rule.
|
||||
base << "uppercase"
|
||||
# Marker mode (Beta / Canary / NEW): rounded-md (slight chip
|
||||
# shape), uppercase, sub-12px text, wider tracking.
|
||||
# text-[10/11px] stays as arbitrary values — the pill is
|
||||
# intentionally sub-12px (Sure's smallest scale token is text-xs
|
||||
# / 12px) so it reads as a marker, not a label. Padding / gap /
|
||||
# tracking snap to Tailwind's scale to satisfy the design-system
|
||||
# "no arbitrary values" rule.
|
||||
base << "rounded-md uppercase"
|
||||
base << (size == :md ? "px-2 py-0.5 text-[11px] tracking-wide gap-1" : "px-1.5 py-0.5 text-[10px] tracking-wider gap-1")
|
||||
else
|
||||
# Badge mode (Pending / Active / Past due / category tag):
|
||||
# normal case, snaps to the design-system text scale
|
||||
# (`text-xs` / `text-sm`). Padding bumps slightly so the badge
|
||||
# reads as a status chip rather than a sub-12px marker.
|
||||
# rounded-full pill shape (matches the existing convention used
|
||||
# by `settings/providers/_status_pill`, `_maturity_badge`, and
|
||||
# the inline transaction badges). Normal case, snaps to the
|
||||
# design-system text scale (`text-xs` / `text-sm`).
|
||||
base << "rounded-full"
|
||||
base << (size == :md ? "px-2 py-0.5 text-sm gap-1.5" : "px-1.5 py-0.5 text-xs gap-1")
|
||||
end
|
||||
class_names(*base)
|
||||
|
||||
@@ -22,10 +22,13 @@
|
||||
<%= entry.date ? I18n.l(entry.date, format: :long) : "—" %>
|
||||
</span>
|
||||
<% if entry.transaction.pending? %>
|
||||
<span class="inline-flex items-center gap-1 text-xs font-medium rounded-full px-1.5 py-0.5 border border-secondary text-secondary" title="<%= t("transactions.transaction.pending_tooltip") %>">
|
||||
<%= icon "clock", size: "sm", color: "current" %>
|
||||
<%= t("transactions.transaction.pending") %>
|
||||
</span>
|
||||
<%= render DS::Pill.new(
|
||||
label: t("transactions.transaction.pending"),
|
||||
tone: :neutral,
|
||||
marker: false,
|
||||
icon: "clock",
|
||||
title: t("transactions.transaction.pending_tooltip")
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,10 +36,12 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1 flex-shrink-0">
|
||||
<span class="inline-flex items-center gap-1 text-xs font-medium rounded-full px-1.5 py-0.5 border border-secondary bg-surface-inset text-secondary">
|
||||
<%= icon "split", size: "sm", color: "current" %>
|
||||
<%= t("transactions.split_parent_row.split_label") %>
|
||||
</span>
|
||||
<%= render DS::Pill.new(
|
||||
label: t("transactions.split_parent_row.split_label"),
|
||||
tone: :neutral,
|
||||
marker: false,
|
||||
icon: "split"
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -98,33 +98,45 @@
|
||||
|
||||
<%# Pending indicator %>
|
||||
<% if transaction.pending? %>
|
||||
<span class="inline-flex items-center gap-1 text-xs font-medium rounded-full px-1.5 py-0.5 border border-secondary text-secondary" title="<%= t("transactions.transaction.pending_tooltip") %>">
|
||||
<%= icon "clock", size: "sm", color: "current" %>
|
||||
<%= t("transactions.transaction.pending") %>
|
||||
</span>
|
||||
<%= render DS::Pill.new(
|
||||
label: t("transactions.transaction.pending"),
|
||||
tone: :neutral,
|
||||
marker: false,
|
||||
icon: "clock",
|
||||
title: t("transactions.transaction.pending_tooltip")
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<%# Potential duplicate indicator - different styling for low vs medium confidence %>
|
||||
<% if transaction.has_potential_duplicate? %>
|
||||
<% if transaction.low_confidence_duplicate? %>
|
||||
<span class="inline-flex items-center gap-1 text-xs font-medium rounded-full px-1.5 py-0.5 border border-secondary bg-surface-inset text-secondary" title="<%= t("transactions.transaction.review_recommended_tooltip") %>">
|
||||
<%= icon "help-circle", size: "sm", color: "current" %>
|
||||
<%= t("transactions.transaction.review_recommended") %>
|
||||
</span>
|
||||
<%= render DS::Pill.new(
|
||||
label: t("transactions.transaction.review_recommended"),
|
||||
tone: :neutral,
|
||||
marker: false,
|
||||
icon: "help-circle",
|
||||
title: t("transactions.transaction.review_recommended_tooltip")
|
||||
) %>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center gap-1 text-xs font-medium rounded-full px-1.5 py-0.5 border border-warning bg-warning/10 text-warning" title="<%= t("transactions.transaction.potential_duplicate_tooltip") %>">
|
||||
<%= icon "alert-triangle", size: "sm", color: "current" %>
|
||||
<%= t("transactions.transaction.possible_duplicate") %>
|
||||
</span>
|
||||
<%= render DS::Pill.new(
|
||||
label: t("transactions.transaction.possible_duplicate"),
|
||||
tone: :warning,
|
||||
marker: false,
|
||||
icon: "alert-triangle",
|
||||
title: t("transactions.transaction.potential_duplicate_tooltip")
|
||||
) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%# Split indicator %>
|
||||
<% if @split_parent_entry_ids ? @split_parent_entry_ids.include?(entry.id) : entry.split_parent? %>
|
||||
<span class="inline-flex items-center gap-1 text-xs font-medium rounded-full px-1.5 py-0.5 border border-secondary bg-surface-inset text-secondary" title="<%= t("transactions.transaction.split_tooltip") %>">
|
||||
<%= icon "split", size: "sm", color: "current" %>
|
||||
<%= t("transactions.transaction.split") %>
|
||||
</span>
|
||||
<%= render DS::Pill.new(
|
||||
label: t("transactions.transaction.split"),
|
||||
tone: :neutral,
|
||||
marker: false,
|
||||
icon: "split",
|
||||
title: t("transactions.transaction.split_tooltip")
|
||||
) %>
|
||||
<% end %>
|
||||
<% if entry.split_child? && !in_split_group %>
|
||||
<span class="text-secondary" title="<%= t("transactions.transaction.split_child_tooltip") %>">
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
require "test_helper"
|
||||
|
||||
class DS::PillTest < ViewComponent::TestCase
|
||||
test "marker mode (default) renders uppercase sub-12px chrome" do
|
||||
test "marker mode (default) renders uppercase sub-12px chrome with rounded-md" do
|
||||
render_inline(DS::Pill.new(label: "Beta", tone: :violet))
|
||||
|
||||
pill = page.find("span", text: "Beta")
|
||||
assert_includes pill[:class], "uppercase"
|
||||
# Marker keeps sub-12px text via arbitrary value (intentional — see component docs).
|
||||
assert_match(/text-\[1[01]px\]/, pill[:class])
|
||||
# Marker uses rounded-md (chip shape).
|
||||
assert_includes pill[:class], "rounded-md"
|
||||
refute_includes pill[:class], "rounded-full"
|
||||
end
|
||||
|
||||
test "marker: false renders normal-case DS-scale chrome" do
|
||||
test "marker: false renders normal-case DS-scale chrome with rounded-full" do
|
||||
render_inline(DS::Pill.new(label: "Active", tone: :success, marker: false))
|
||||
|
||||
pill = page.find("span", text: "Active")
|
||||
@@ -18,6 +21,9 @@ class DS::PillTest < ViewComponent::TestCase
|
||||
# Badge mode snaps to text-xs / text-sm — no sub-12px arbitrary values.
|
||||
assert_match(/text-(xs|sm)/, pill[:class])
|
||||
refute_match(/text-\[1[01]px\]/, pill[:class])
|
||||
# Badge uses rounded-full to match the existing _status_pill / _maturity_badge convention.
|
||||
assert_includes pill[:class], "rounded-full"
|
||||
refute_includes pill[:class], "rounded-md"
|
||||
end
|
||||
|
||||
test "semantic tone aliases resolve to visual palette tones" do
|
||||
@@ -51,9 +57,9 @@ class DS::PillTest < ViewComponent::TestCase
|
||||
# Lucide icon helper renders the inline SVG; verifying we see at least one <svg>
|
||||
# is enough — the icon helper is covered by its own tests.
|
||||
assert_selector "svg"
|
||||
# And the dot is suppressed when an icon takes its place. `refute_selector
|
||||
# ..., count: N` only fails when there are exactly N matches, so use
|
||||
# `assert_no_selector` to strictly assert zero dots.
|
||||
assert_no_selector "span.rounded-full[style*='background-color']"
|
||||
# And the dot is suppressed when an icon takes its place. The dot is an
|
||||
# `inline-block` span (parent pill is `inline-flex`), so target it by
|
||||
# `inline-block.rounded-full` to avoid matching the parent pill.
|
||||
assert_no_selector "span.inline-block.rounded-full"
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user