Files
sure/test/components/previews/pill_component_preview.rb
Guillem Arias Fauste 09058b0cc6 feat(design-system): extend DS::Pill with badge mode + semantic tones (#1751 PR A) (#1902)
* feat(design-system): extend DS::Pill with badge mode + semantic tones (#1751)

Adds two extensions to the existing `DS::Pill` (originally landed as a
stage marker primitive in #1829) so it can also serve as the shared
status / category badge across the app — the use case tracked by #1751.

**Badge mode (`marker: false`)**

The original `DS::Pill` was intentionally sub-12px (text-[10px] /
text-[11px]) + uppercase + tracking-wide so it reads as a marker
(`Beta`, `Canary`, `NEW`), not a label. That shape is wrong for
status badges where the surrounding context is regular UI copy and the
pill needs to feel like a chip (`Pending`, `Active`, `Past due`,
`Failed`).

The new `marker: false` flag drops the uppercase + arbitrary
sub-12px text and snaps the chrome to the DS text scale:

- `marker: false, size: :sm` → `text-xs` (12px), normal case
- `marker: false, size: :md` → `text-sm` (14px), normal case
- `marker: true` (default) → existing #1829 behavior, unchanged

**Semantic tone aliases**

Status badges read more naturally with semantic tone names than with
the underlying palette colors:

| Alias | Resolves to |
|---|---|
| `:success` | `:green` |
| `:warning` | `:amber` |
| `:error` / `:destructive` | `:red` (new tone, added here) |
| `:info` | `:indigo` |
| `:neutral` | `:gray` |

Visual-name tones (`:violet`, `:indigo`, `:fuchsia`, `:amber`,
`:green`, `:gray`, `:red`) still work as before — semantic aliases
resolve through `SEMANTIC_TONE_ALIASES` at component init time, so
the callsite can pick whichever name reads better. Unknown tones
still fall back to `:violet` (existing behavior).

**Red palette**

Adds the `:red` tone (palette already present in
`design/tokens/sure.tokens.json` — `red-50/100/200/500/700/tint-10`).
Needed for `:error` / `:destructive` status badges.

**Icon slot**

Adds an `icon:` option (already documented in the component's
doc-comment as planned). When set, the Lucide glyph replaces the
colored dot inside the pill — useful for status badges that read
better with a glyph (`circle-check`, `triangle-alert`,
`loader`, etc.) than the generic dot.

**Scope**

API + tests + Lookbook preview only. No callsite migrations in this
PR — that's the next slice of #1751, done as separate per-bucket PRs
(transaction badges, provider badges, misc) to keep diffs small.

DS::Pill currently has no in-app callsites (#1829 shipped the
primitive ahead of consumers), so this is a pure-additive change.
Existing API is fully backwards-compatible — `marker:` defaults to
`true`, so without that flag the pill renders exactly as it does
today.

* fix(test): use assert_no_selector for dot-suppression assertion

`refute_selector ..., count: 1` only fails when there are exactly 1
matches — it would silently pass for 0 OR 2+. The intent is "no dots
should render when an icon is set"; `assert_no_selector` strictly
asserts zero matches.

Flagged by coderabbit on #1902.
2026-05-22 02:16:33 +02:00

69 lines
1.9 KiB
Ruby

class PillComponentPreview < ViewComponent::Preview
# @param tone select ["violet", "indigo", "fuchsia", "amber", "green", "gray", "red", "success", "warning", "error", "info", "neutral"]
# @param style select ["soft", "filled", "outline"]
# @param size select ["sm", "md"]
# @param label text
# @param show_dot toggle
# @param dot_only toggle
# @param marker toggle
# @param icon text
def default(tone: "violet", style: "soft", size: "sm", label: "Preview", show_dot: true, dot_only: false, marker: true, icon: nil)
render DS::Pill.new(
label: label,
tone: tone.to_sym,
style: style.to_sym,
size: size.to_sym,
show_dot: show_dot,
dot_only: dot_only,
marker: marker,
icon: icon.presence
)
end
# @!group Stage markers (marker: true — original #1829 shape)
def canary
render DS::Pill.new(label: "Canary", tone: :fuchsia)
end
def beta
render DS::Pill.new(label: "Beta", tone: :violet)
end
def new_marker
render DS::Pill.new(label: "New", tone: :indigo)
end
def dot_only_collapsed_sidebar
render DS::Pill.new(dot_only: true, tone: :violet)
end
# @!endgroup
# @!group Status badges (marker: false, semantic tones)
def status_active
render DS::Pill.new(label: "Active", tone: :success, marker: false)
end
def status_pending
render DS::Pill.new(label: "Pending", tone: :warning, marker: false)
end
def status_failed
render DS::Pill.new(label: "Failed", tone: :error, marker: false, icon: "circle-alert")
end
def status_archived
render DS::Pill.new(label: "Archived", tone: :neutral, marker: false)
end
def status_info
render DS::Pill.new(label: "Syncing", tone: :info, marker: false, icon: "loader")
end
# @!endgroup
# @!group Sizes (md)
def status_md
render DS::Pill.new(label: "Past due", tone: :error, marker: false, size: :md)
end
# @!endgroup
end