style: consistent badges with sufficient color contrast
This commit is contained in:
parent
d614ad2219
commit
d0b8cb672a
22 changed files with 534 additions and 77 deletions
|
|
@ -145,6 +145,101 @@ defmodule MvWeb.CoreComponents do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Renders a non-interactive badge with WCAG-compliant contrast.
|
||||
|
||||
Use for status labels, counts, or tags. For clickable elements (e.g. filter chips),
|
||||
use a button or link component instead, not this badge.
|
||||
|
||||
## Variants and styles
|
||||
|
||||
- **variant:** `:neutral`, `:primary`, `:info`, `:success`, `:warning`, `:error`
|
||||
- **style:** `:soft` (default, tinted background), `:solid`, `:outline`
|
||||
- **size:** `:sm`, `:md` (default)
|
||||
|
||||
Outline and soft styles always use a visible background so the badge remains
|
||||
readable on base-200/base-300 surfaces (WCAG 2.2 AA). Ghost style is not exposed
|
||||
by default to avoid low-contrast on gray backgrounds.
|
||||
|
||||
## Examples
|
||||
|
||||
<.badge variant="success">Paid</.badge>
|
||||
<.badge variant="error" style="solid">Unpaid</.badge>
|
||||
<.badge variant="neutral" size="sm">Custom</.badge>
|
||||
<.badge variant="primary" style="outline">Label</.badge>
|
||||
<.badge variant="success" sr_label="Paid">
|
||||
<.icon name="hero-check-circle" class="size-4" />
|
||||
</.badge>
|
||||
"""
|
||||
attr :variant, :any,
|
||||
default: "neutral",
|
||||
doc: "Color variant: neutral | primary | info | success | warning | error (string or atom)"
|
||||
|
||||
attr :style, :any,
|
||||
default: "soft",
|
||||
doc: "Visual style: soft | solid | outline; :outline gets bg-base-100 for contrast"
|
||||
|
||||
attr :size, :any,
|
||||
default: "md",
|
||||
doc: "Badge size: sm | md"
|
||||
|
||||
attr :sr_label, :string,
|
||||
default: nil,
|
||||
doc: "Optional screen-reader label for icon-only content"
|
||||
|
||||
attr :rest, :global, doc: "Arbitrary HTML attributes (e.g. id, class, data-testid)"
|
||||
|
||||
slot :inner_block, required: true, doc: "Badge text (and optional icon)"
|
||||
slot :icon, doc: "Optional leading icon slot"
|
||||
|
||||
def badge(assigns) do
|
||||
# Normalize so both HEEx strings (variant="neutral") and helper atoms (variant={:neutral}) work
|
||||
variant = to_string(assigns.variant || "neutral")
|
||||
style = to_string(assigns.style || "soft")
|
||||
size = to_string(assigns.size || "md")
|
||||
|
||||
variant_class = "badge-#{variant}"
|
||||
style_class = badge_style_class(style)
|
||||
size_class = "badge-#{size}"
|
||||
# Outline has transparent bg in DaisyUI; add bg so it stays visible on base-200/base-300
|
||||
outline_bg = if style == "outline", do: "bg-base-100", else: nil
|
||||
|
||||
rest = assigns.rest || []
|
||||
rest = if is_list(rest), do: rest, else: Map.to_list(rest)
|
||||
extra_class = Keyword.get(rest, :class)
|
||||
rest = Keyword.drop(rest, [:class])
|
||||
rest = if assigns.sr_label, do: Keyword.put(rest, :"aria-label", assigns.sr_label), else: rest
|
||||
|
||||
class =
|
||||
["badge", variant_class, style_class, size_class, outline_bg, extra_class]
|
||||
|> List.flatten()
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> Enum.join(" ")
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:class, class)
|
||||
|> assign(:rest, rest)
|
||||
|> assign(:has_icon, assigns.icon != [])
|
||||
|
||||
~H"""
|
||||
<span class={@class} {@rest}>
|
||||
<%= if @has_icon do %>
|
||||
{render_slot(@icon)}
|
||||
<% end %>
|
||||
{render_slot(@inner_block)}
|
||||
<%= if @sr_label do %>
|
||||
<span class="sr-only">{@sr_label}</span>
|
||||
<% end %>
|
||||
</span>
|
||||
"""
|
||||
end
|
||||
|
||||
defp badge_style_class("soft"), do: "badge-soft"
|
||||
defp badge_style_class("solid"), do: nil
|
||||
defp badge_style_class("outline"), do: "badge-outline"
|
||||
defp badge_style_class(_), do: nil
|
||||
|
||||
@doc """
|
||||
Renders a dropdown menu.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue