diff --git a/app/components/DS/pill.html.erb b/app/components/DS/pill.html.erb index 6712f0602..2dc15e0ec 100644 --- a/app/components/DS/pill.html.erb +++ b/app/components/DS/pill.html.erb @@ -9,7 +9,9 @@ title="<%= title || t("ds.pill.aria_label", label: label) %>"> <% else %> - <% if show_dot %> + <% if icon %> + <%= helpers.icon(icon, size: "xs", color: "current") %> + <% elsif show_dot %> <% end %> diff --git a/app/components/DS/pill.rb b/app/components/DS/pill.rb index 05733c4f4..e3c7ac2b3 100644 --- a/app/components/DS/pill.rb +++ b/app/components/DS/pill.rb @@ -1,20 +1,23 @@ class DS::Pill < DesignSystemComponent - TONES = %i[violet indigo fuchsia amber gray].freeze + TONES = %i[violet indigo fuchsia amber green gray].freeze STYLES = %i[soft filled outline].freeze SIZES = %i[sm md].freeze - attr_reader :label, :tone, :style, :size, :show_dot, :dot_only, :title + attr_reader :label, :tone, :style, :size, :show_dot, :dot_only, :title, :icon - # Generic inline pill primitive. Currently the home of Beta / Canary - # markers; can be reused for future tags (NEW, PRO, EXPERIMENTAL, etc.) - # without forking the component. + # Generic inline pill primitive. Used for Beta / Canary markers and goal + # status badges, but designed so any future tag (NEW, PRO, EXPERIMENTAL, + # etc.) reuses the same shape without forking. # # - `dot_only: true` renders only the colored dot (no label, no border). # Use on the collapsed sidebar nav, where there's no room for the label. - # - Sure has full violet / indigo / fuchsia / amber / gray ramps in the - # design system; this component picks named tokens at render time. No - # raw hex. - def initialize(label: nil, tone: :violet, style: :soft, size: :sm, show_dot: true, dot_only: false, title: nil) + # - `icon:` overrides the dot with a Lucide icon (sized xs, current color). + # Useful for status pills that benefit from a glyph (circle-check, + # triangle-alert, pause, etc.) rather than the generic dot. + # - Sure has full violet / indigo / fuchsia / amber / green / gray ramps + # in the design system; this component picks named tokens at render + # time. No raw hex. + def initialize(label: nil, tone: :violet, style: :soft, size: :sm, show_dot: true, dot_only: false, title: nil, icon: nil) @label = label || I18n.t("ds.pill.default_label", default: "Beta") @tone = TONES.include?(tone.to_sym) ? tone.to_sym : :violet @style = STYLES.include?(style.to_sym) ? style.to_sym : :soft @@ -22,6 +25,7 @@ class DS::Pill < DesignSystemComponent @show_dot = show_dot @dot_only = dot_only @title = title + @icon = icon end def palette @@ -34,6 +38,7 @@ class DS::Pill < DesignSystemComponent indigo: { bg: "var(--color-indigo-50)", bg_dark: "var(--color-indigo-tint-10)", text: "color-mix(in oklab, var(--color-indigo-700), black 30%)", text_dark: "var(--color-indigo-200)", border: "var(--color-indigo-200)", dot: "var(--color-indigo-500)", fill: "var(--color-indigo-500)" }, fuchsia: { bg: "var(--color-fuchsia-50)", bg_dark: "var(--color-fuchsia-tint-10)", text: "color-mix(in oklab, var(--color-fuchsia-700), black 30%)", text_dark: "var(--color-fuchsia-200)", border: "var(--color-fuchsia-200)", dot: "var(--color-fuchsia-500)", fill: "var(--color-fuchsia-500)" }, amber: { bg: "var(--color-yellow-50)", bg_dark: "var(--color-yellow-tint-10)", text: "color-mix(in oklab, var(--color-yellow-700), black 30%)", text_dark: "var(--color-yellow-200)", border: "var(--color-yellow-200)", dot: "var(--color-yellow-500)", fill: "var(--color-yellow-500)" }, + green: { bg: "var(--color-green-50)", bg_dark: "var(--color-green-tint-10)", text: "color-mix(in oklab, var(--color-green-700), black 30%)", text_dark: "var(--color-green-200)", border: "var(--color-green-200)", dot: "var(--color-green-500)", fill: "var(--color-green-500)" }, gray: { bg: "var(--color-gray-100)", bg_dark: "var(--color-gray-tint-10)", text: "color-mix(in oklab, var(--color-gray-700), black 30%)", text_dark: "var(--color-gray-200)", border: "var(--color-gray-200)", dot: "var(--color-gray-500)", fill: "var(--color-gray-500)" } }[tone] end diff --git a/app/components/goals/status_pill_component.html.erb b/app/components/goals/status_pill_component.html.erb index 548a0b37a..e2f64df78 100644 --- a/app/components/goals/status_pill_component.html.erb +++ b/app/components/goals/status_pill_component.html.erb @@ -1,4 +1 @@ - - <%= helpers.icon(icon_name, size: "xs", color: "current") %> - <%= label %> - +<%= render DS::Pill.new(label: label, tone: variant[:tone], style: :outline, icon: variant[:icon]) %> diff --git a/app/components/goals/status_pill_component.rb b/app/components/goals/status_pill_component.rb index d1ec2179f..134500b62 100644 --- a/app/components/goals/status_pill_component.rb +++ b/app/components/goals/status_pill_component.rb @@ -1,19 +1,17 @@ class Goals::StatusPillComponent < ApplicationComponent - # Text colors here intentionally use palette steps (green/yellow/gray-700) - # instead of the `text-success` / `text-warning` / `text-secondary` tokens - # because the functional tokens drop below WCAG 1.4.3 4.5:1 on tinted - # surfaces in light mode (~2.88:1 / 3.0:1 / 4.16:1). Each variant carries - # a theme-dark: override so the dark-700 text doesn't disappear against - # the dark-mode tinted surface. Local override only; revert once - # we-promise/sure#1736 lands token-level fixes. + # Maps the goal's display_status to the DS::Pill primitive's tone + + # glyph. Outline style is used so the pill keeps its colored border on + # any card background (resting bg-container, hover bg-surface-hover); + # the filled / soft variants blended into the hover state and lost + # contrast on cards. VARIANTS = { - on_track: { classes: "bg-green-500/10 text-green-700 theme-dark:text-green-300", icon: "circle-check" }, - behind: { classes: "bg-surface-inset text-yellow-700 theme-dark:text-yellow-300", icon: "triangle-alert" }, - reached: { classes: "bg-green-500/10 text-green-700 theme-dark:text-green-300", icon: "star" }, - completed: { classes: "bg-green-500/10 text-green-700 theme-dark:text-green-300", icon: "circle-check-big" }, - no_target_date: { classes: "bg-surface-inset text-gray-700 theme-dark:text-gray-200", icon: "infinity" }, - paused: { classes: "bg-surface-inset text-gray-700 theme-dark:text-gray-200", icon: "pause" }, - archived: { classes: "bg-surface-inset text-gray-700 theme-dark:text-gray-200", icon: "archive" } + on_track: { tone: :green, icon: "circle-check" }, + behind: { tone: :amber, icon: "triangle-alert" }, + reached: { tone: :green, icon: "star" }, + completed: { tone: :green, icon: "circle-check-big" }, + no_target_date: { tone: :gray, icon: "infinity" }, + paused: { tone: :gray, icon: "pause" }, + archived: { tone: :gray, icon: "archive" } }.freeze def initialize(goal:) @@ -31,12 +29,4 @@ class Goals::StatusPillComponent < ApplicationComponent def label I18n.t("goals.status.#{status_key}", default: status_key.to_s.titleize) end - - def classes - variant[:classes] - end - - def icon_name - variant[:icon] - end end