Search documentation of phoenix_kit

# PhoenixKit.Dashboard.Badge (phoenix_kit v1.7.87)

Copy Markdown View Source

Defines badge types for dashboard tab indicators.

Badges provide visual feedback on tabs to show counts, status, or draw attention. They can be static or update live via PubSub subscriptions.

## Badge Types

- `:count` - Numeric count badge (e.g., "5", "99+")
- `:dot` - Simple colored dot indicator
- `:status` - Status indicator with color (green/yellow/red)
- `:new` - "New" text indicator
- `:text` - Custom text badge

## Static Badge

    %Badge{type: :count, value: 5}
    %Badge{type: :dot, color: :success}
    %Badge{type: :status, value: :online, color: :success}

## Live Badge with PubSub

    %Badge{
      type: :count,
      subscribe: {"farm:stats", fn msg -> msg.printing_count end}
    }

## Context-Aware Badge

For badges that show different values per user/organization/context, use `context_key` and optionally `loader`. The badge value is stored per-context in socket assigns instead of globally.

    # Badge depends on current organization context
    %Badge{
      type: :count,
      context_key: :organization,
      loader: {MyApp.Alerts, :count_for_org},  # Called with (context)
      subscribe: "org:{id}:alerts"  # {id} replaced with context.id
    }

### Context Placeholders

Subscribe topics support `{field}` placeholders that are resolved from the current context:

- `{id}` - context.id or context[:id]
- `{name}` - context.name or context[:name]
- Any field accessible on the context struct/map

### Loader Function

The loader is called when the LiveView mounts to get the initial badge value:

- `{Module, :function}` - Called as `Module.function(context)`
- `fn context -> value end` - Anonymous function

## Badge with Attention Animation

    %Badge{
      type: :count,
      value: 3,
      color: :error,
      pulse: true
    }

## Summary

### Types

badge_color()
badge_type()
compound_segment()
compound_style()
loader_config()
subscribe_config()
t()

### Functions

color_class(arg1)

Returns the CSS color class for the badge.

compound(segments, opts \\ [])

Creates a compound badge with multiple colored segments.

compound_context(context_key, loader, opts \\ [])

Creates a context-aware compound badge that loads segments per-context.

context(context_key, loader, opts \\ [])

Creates a context-aware badge that loads values per-context.

context_aware?(badge)

Checks if this badge is context-aware (requires per-context value storage).

count(value, opts \\ [])

Creates a count badge.

display_value(badge)

Formats the badge value for display.

dot(opts \\ [])

Creates a dot badge.

dot_color_class(arg1)

Returns the CSS background color class for dot badges.

extract_value(arg1, message)

Extracts value from a PubSub message using the badge's subscription config.

get_resolved_topic(badge, context)

Gets the resolved topic for this badge given a context.

get_topic(arg1)

Gets the PubSub topic for this badge, if it has a subscription.

live(topic, extractor, opts \\ [])

Creates a live badge that subscribes to PubSub updates.

live?(badge)

Checks if this badge has a live subscription.

load_value(arg1, context)

Loads the initial badge value using the loader function.

new(attrs)

Creates a new Badge struct from a map or keyword list.

new!(attrs)

Creates a new Badge struct, raising on error.

new_indicator(opts \\ [])

Creates a "New" badge.

resolve_topic(topic, context)

Resolves placeholders in a topic string using context data.

status(value, opts \\ [])

Creates a status badge.

text(value, opts \\ [])

Creates a text badge with custom text.

update_segments(badge, segments)

Updates segments for a compound badge.

update_value(badge, value)

Updates the badge value.

visible?(badge)

Checks if the badge should be visible.

visible_segments(arg1)

Returns visible segments for a compound badge.

## Types

# badge_color()

    @type badge_color() ::
      :primary
      | :secondary
      | :accent
      | :info
      | :success
      | :warning
      | :error
      | :neutral

# badge_type()

    @type badge_type() :: :count | :dot | :status | :new | :text | :compound

# compound_segment()

    @type compound_segment() :: %{
      :value => integer() | String.t(),
      :color => badge_color(),
      optional(:label) => String.t()
    }

# compound_style()

    @type compound_style() :: :text | :blocks | :dots

# loader_config()

    @type loader_config() :: {module(), atom()} | (any() -> any())

# subscribe_config()

    @type subscribe_config() ::
      {String.t(), (map() -> any())} | {String.t(), atom()} | String.t()

# t()

    @type t() :: %PhoenixKit.Dashboard.Badge{
      animate: boolean(),
      color: badge_color(),
      compound_style: compound_style(),
      context_key: atom() | nil,
      format: (any() -> String.t()) | nil,
      hidden_when_zero: boolean(),
      hide_zero_segments: boolean(),
      loader: loader_config() | nil,
      max: integer() | nil,
      metadata: map(),
      pulse: boolean(),
      segments: [compound_segment()],
      separator: String.t(),
      subscribe: subscribe_config() | nil,
      type: badge_type(),
      value: any()
    }

## Functions

# color_class(arg1)

    @spec color_class(t()) :: String.t()

Returns the CSS color class for the badge.

# compound(segments, opts \\ [])

    @spec compound(
      [compound_segment()],
      keyword()
    ) :: t()

Creates a compound badge with multiple colored segments.

A compound badge displays multiple values with different colors in a single badge. Useful for showing status breakdowns like "10 success / 5 pending / 2 error".

## Styles

- `:text` (default) - Colored text values with separator (e.g., "10 / 5 / 2")
- `:blocks` - Colored background pills side by side
- `:dots` - Colored dots with numbers beside them

## Options

- `:style` - Display style: `:text`, `:blocks`, `:dots` (default: `:text`)
- `:separator` - Separator string for `:text` style (default: "/")
- `:hide_zero_segments` - Hide segments with value 0 (default: false)
- `:pulse` - Enable pulse animation (default: false)

## Segment Format

Each segment is a map with:

- `:value` (required) - Integer or string value to display
- `:color` (required) - Badge color atom (:success, :warning, :error, etc.)
- `:label` (optional) - Text label shown after value (e.g., "done", "pending")

## Examples

    # Simple compound badge
    Badge.compound([
      %{value: 10, color: :success},
      %{value: 5, color: :warning},
      %{value: 2, color: :error}
    ])

    # With labels and blocks style
    Badge.compound([
      %{value: 10, color: :success, label: "done"},
      %{value: 5, color: :warning, label: "pending"}
    ], style: :blocks)

    # Hide zero segments
    Badge.compound([
      %{value: 10, color: :success},
      %{value: 0, color: :error}
    ], hide_zero_segments: true)
    # Only shows: "10"

# compound_context(context_key, loader, opts \\ [])

    @spec compound_context(atom(), loader_config(), keyword()) :: t()

Creates a context-aware compound badge that loads segments per-context.

The loader function should return a list of segment maps.

## Examples

    # Loader returns list of segments for current organization
    Badge.compound_context(:organization, {MyApp.Tasks, :get_status_counts},
      style: :blocks
    )

    # In MyApp.Tasks
    def get_status_counts(org) do
      [
        %{value: count_completed(org.id), color: :success},
        %{value: count_pending(org.id), color: :warning},
        %{value: count_overdue(org.id), color: :error}
      ]
    end

# context(context_key, loader, opts \\ [])

    @spec context(atom(), loader_config(), keyword()) :: t()

Creates a context-aware badge that loads values per-context.

## Examples

    # Badge that shows alert count for current organization
    Badge.context(:organization, {MyApp.Alerts, :count_for_org}, color: :error)

    # With live updates via context-specific PubSub topic
    Badge.context(:farm, {MyApp.Farms, :printing_count},
      subscribe: "farm:{id}:stats",
      color: :info
    )

# context_aware?(badge)

    @spec context_aware?(t()) :: boolean()

Checks if this badge is context-aware (requires per-context value storage).

# count(value, opts \\ [])

    @spec count(
      integer(),
      keyword()
    ) :: t()

Creates a count badge.

## Examples

    Badge.count(5)
    Badge.count(10, color: :error, max: 99)

# display_value(badge)

    @spec display_value(t()) :: String.t() | nil

Formats the badge value for display.

## Examples

    iex> badge = Badge.count(5)
    iex> Badge.display_value(badge)
    "5"

    iex> badge = Badge.count(150, max: 99)
    iex> Badge.display_value(badge)
    "99+"

# dot(opts \\ [])

    @spec dot(keyword()) :: t()

Creates a dot badge.

## Examples

    Badge.dot()
    Badge.dot(color: :success)
    Badge.dot(color: :error, pulse: true)

# dot_color_class(arg1)

    @spec dot_color_class(t()) :: String.t()

Returns the CSS background color class for dot badges.

# extract_value(arg1, message)

    @spec extract_value(t(), map()) :: any()

Extracts value from a PubSub message using the badge's subscription config.

# get_resolved_topic(badge, context)

    @spec get_resolved_topic(t(), map() | struct() | nil) :: String.t() | nil

Gets the resolved topic for this badge given a context.

For context-aware badges, resolves placeholders. For regular badges, returns the topic as-is.

# get_topic(arg1)

    @spec get_topic(t()) :: String.t() | nil

Gets the PubSub topic for this badge, if it has a subscription.

# live(topic, extractor, opts \\ [])

    @spec live(String.t(), atom() | (map() -> any()), keyword()) :: t()

Creates a live badge that subscribes to PubSub updates.

## Examples

    Badge.live("user:notifications", :count)
    Badge.live("farm:stats", fn msg -> msg.printing_count end, color: :info)

# live?(badge)

    @spec live?(t()) :: boolean()

Checks if this badge has a live subscription.

# load_value(arg1, context)

    @spec load_value(t(), map() | struct() | nil) :: any()

Loads the initial badge value using the loader function.

## Examples

    iex> badge = Badge.context(:org, {MyApp.Alerts, :count_for_org})
    iex> Badge.load_value(badge, %{id: 123})
    5  # Result from MyApp.Alerts.count_for_org(%{id: 123})

# new(attrs)

    @spec new(map() | keyword()) :: {:ok, t()} | {:error, String.t()}

Creates a new Badge struct from a map or keyword list.

## Options

- `:type` - Badge type: :count, :dot, :status, :new, :text (default: :count)
- `:value` - The value to display (number for count, atom/string for status/text)
- `:color` - Badge color: :primary, :secondary, :accent, :info, :success, :warning, :error, :neutral (default: :primary)
- `:max` - Maximum display value for count badges (e.g., 99 shows "99+") (optional)
- `:pulse` - Enable pulse animation (default: false)
- `:animate` - Enable value change animation (default: true)
- `:hidden_when_zero` - Hide count badge when value is 0 (default: true)
- `:subscribe` - PubSub subscription config for live updates (optional)
- `:format` - Custom formatter function for the value (optional)
- `:metadata` - Custom metadata map (default: %{})
- `:context_key` - Context selector key for per-context badges (optional, e.g., :organization)
- `:loader` - Function to load initial value for context: `{Module, :function}` or `fn context -> value end`

## Subscribe Configuration

The `:subscribe` option can be:

1. A tuple of {topic, extractor_function}: `{"farm:stats", fn msg -> msg.printing_count end}`

2. A tuple of {topic, key_atom} to extract from message: `{"farm:stats", :printing_count}`

3. Just a topic string (uses full message as value): `"user:notifications:count"`

Topics support `{field}` placeholders for context-aware badges: `"org:{id}:alerts"` - resolves to `"org:123:alerts"` when context.id is 123

## Examples

    iex> Badge.new(type: :count, value: 5)
    {:ok, %Badge{type: :count, value: 5}}

    iex> Badge.new(type: :dot, color: :error, pulse: true)
    {:ok, %Badge{type: :dot, color: :error, pulse: true}}

    iex> Badge.new(type: :count, subscribe: {"orders:count", :count})
    {:ok, %Badge{type: :count, subscribe: {"orders:count", :count}}}

    # Context-aware badge
    iex> Badge.new(type: :count, context_key: :organization, loader: {MyApp.Alerts, :count_for_org})
    {:ok, %Badge{type: :count, context_key: :organization, loader: {MyApp.Alerts, :count_for_org}}}

# new!(attrs)

    @spec new!(map() | keyword()) :: t()

Creates a new Badge struct, raising on error.

# new_indicator(opts \\ [])

    @spec new_indicator(keyword()) :: t()

Creates a "New" badge.

## Examples

    Badge.new_indicator()
    Badge.new_indicator(color: :accent)

# resolve_topic(topic, context)

    @spec resolve_topic(String.t() | nil, map() | struct()) :: String.t() | nil

Resolves placeholders in a topic string using context data.

Supports `{field}` syntax where field is accessed from the context.

## Examples

    iex> Badge.resolve_topic("org:{id}:alerts", %{id: 123})
    "org:123:alerts"

    iex> Badge.resolve_topic("farm:{farm_id}:stats", %{farm_id: "abc"})
    "farm:abc:stats"

# status(value, opts \\ [])

    @spec status(
      atom() | String.t(),
      keyword()
    ) :: t()

Creates a status badge.

## Examples

    Badge.status(:online, color: :success)
    Badge.status(:busy, color: :warning)
    Badge.status(:offline, color: :error)

# text(value, opts \\ [])

    @spec text(
      String.t(),
      keyword()
    ) :: t()

Creates a text badge with custom text.

## Examples

    Badge.text("Beta")
    Badge.text("Pro", color: :accent)

# update_segments(badge, segments)

    @spec update_segments(t(), [compound_segment()]) :: t()

Updates segments for a compound badge.

# update_value(badge, value)

    @spec update_value(t(), any()) :: t()

Updates the badge value.

# visible?(badge)

    @spec visible?(t()) :: boolean()

Checks if the badge should be visible.

Count badges with value 0 are hidden by default when hidden_when_zero is true. Compound badges are hidden when all segments have zero value (and hide_zero_segments is true).

# visible_segments(arg1)

    @spec visible_segments(t()) :: [compound_segment()]

Returns visible segments for a compound badge.

If `hide_zero_segments` is true, filters out segments with value 0 or nil.

## Examples

    badge = Badge.compound([
      %{value: 10, color: :success},
      %{value: 0, color: :error}
    ], hide_zero_segments: true)

    Badge.visible_segments(badge)
    # => [%{value: 10, color: :success}]