Packages

phoenix_kit

1.7.69
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules publishing web components version_switcher.ex
Raw

lib/modules/publishing/web/components/version_switcher.ex

defmodule PhoenixKit.Modules.Publishing.Web.Components.VersionSwitcher do
@moduledoc """
Version switcher component for publishing posts.
Displays available versions as a compact inline list with status indicators.
Used in the publishing editor to navigate between different versions of a post.
## Display Format
Admin mode: v1 (●) | v2 (●) | v3 (○)
- Green dot (●): Published (this IS the live version)
- Yellow dot (●): Draft
- Gray dot (●): Archived
- Current version is highlighted
## Examples
# Editor: switch between versions
<.publishing_version_switcher
versions={@available_versions}
version_statuses={@version_statuses}
current_version={@current_version}
on_click="switch_version"
/>
"""
use Phoenix.Component
use Gettext, backend: PhoenixKitWeb.Gettext
@doc """
Renders a compact inline version switcher.
## Attributes
- `versions` - List of version numbers (integers)
- `version_statuses` - Map of version number => status string ("published", "draft", "archived")
- `version_dates` - Map of version number => ISO 8601 date string (shown in tooltip)
- `current_version` - Currently active version number
- `on_click` - Event name for click handler
- `phx_target` - Target for the event
- `class` - Additional CSS classes
- `size` - Size variant: :xs, :sm, :md (default: :sm)
Note: Only ONE version can have status "published" at a time. This is the live version.
"""
attr :versions, :list, required: true
attr :version_statuses, :map, default: %{}
attr :version_dates, :map, default: %{}
attr :current_version, :integer, default: nil
attr :on_click, :string, default: nil
attr :phx_target, :any, default: nil
attr :class, :string, default: ""
attr :size, :atom, default: :sm, values: [:xs, :sm, :md]
def publishing_version_switcher(assigns) do
# Sort versions in ascending order
sorted_versions = Enum.sort(assigns.versions)
# Determine which version is actually "live" (highest published version)
# Only ONE version should show as live - the one users will see publicly
live_version =
sorted_versions
|> Enum.filter(fn v -> Map.get(assigns.version_statuses, v) == "published" end)
|> Enum.max(fn -> nil end)
assigns =
assigns
|> assign(:sorted_versions, sorted_versions)
|> assign(:live_version, live_version)
~H"""
<div class={["flex items-center flex-wrap", size_gap_class(@size), @class]}>
<%= for {version, index} <- Enum.with_index(@sorted_versions) do %>
<%= if index > 0 do %>
<span class={["text-base-content/30", size_separator_class(@size)]}>|</span>
<% end %>
<.version_item
version={version}
status={Map.get(@version_statuses, version)}
date={Map.get(@version_dates, version)}
is_current={version == @current_version}
is_live={version == @live_version}
on_click={@on_click}
phx_target={@phx_target}
size={@size}
/>
<% end %>
</div>
"""
end
attr :version, :integer, required: true
attr :status, :string, default: nil
attr :date, :string, default: nil
attr :is_current, :boolean, default: false
attr :is_live, :boolean, default: false
attr :on_click, :string, default: nil
attr :phx_target, :any, default: nil
attr :size, :atom, default: :sm
defp version_item(assigns) do
~H"""
<%= if @on_click do %>
<button
type="button"
phx-click={@on_click}
phx-value-version={@version}
phx-target={@phx_target}
class={item_classes(@is_current, @size)}
title={version_title(@version, @status, @is_live, @date)}
>
<.version_content
version={@version}
status={@status}
is_current={@is_current}
is_live={@is_live}
size={@size}
/>
</button>
<% else %>
<span
class={item_classes(@is_current, @size)}
title={version_title(@version, @status, @is_live, @date)}
>
<.version_content
version={@version}
status={@status}
is_current={@is_current}
is_live={@is_live}
size={@size}
/>
</span>
<% end %>
"""
end
attr :version, :integer, required: true
attr :status, :string, default: nil
attr :is_current, :boolean, default: false
attr :is_live, :boolean, default: false
attr :size, :atom, default: :sm
defp version_content(assigns) do
~H"""
<span class="inline-flex items-center gap-1">
<span class={status_dot_classes(@status, @is_live, @size)}></span>
<span class={code_classes(@is_current, @size)}>
v{@version}
</span>
<%= if @is_live do %>
<span class="text-xs text-success font-medium">(live)</span>
<% end %>
</span>
"""
end
# Status dot styling - live = green, published (not live) = blue, draft = yellow, archived = gray
defp status_dot_classes(status, is_live, size) do
base = ["rounded-full", "inline-block", dot_size_class(size)]
color =
cond do
is_live -> "bg-success"
status == "published" -> "bg-info"
status == "draft" -> "bg-warning"
status == "archived" -> "bg-base-content/40"
true -> "bg-base-content/20"
end
base ++ [color]
end
# Item container classes
defp item_classes(is_current, size) do
base = [
"inline-flex items-center rounded transition-colors",
size_padding_class(size)
]
state =
if is_current do
"bg-primary/10 text-primary font-semibold"
else
"hover:bg-base-200 cursor-pointer"
end
base ++ [state]
end
# Code text classes
defp code_classes(is_current, size) do
base = [size_text_class(size)]
weight = if is_current, do: "font-semibold", else: "font-medium"
base ++ [weight]
end
# Size-based classes
defp dot_size_class(:xs), do: "w-1.5 h-1.5"
defp dot_size_class(:sm), do: "w-2 h-2"
defp dot_size_class(:md), do: "w-2.5 h-2.5"
defp size_text_class(:xs), do: "text-xs"
defp size_text_class(:sm), do: "text-sm"
defp size_text_class(:md), do: "text-base"
defp size_padding_class(:xs), do: "px-1 py-0.5"
defp size_padding_class(:sm), do: "px-1.5 py-0.5"
defp size_padding_class(:md), do: "px-2 py-1"
defp size_gap_class(:xs), do: "gap-0.5"
defp size_gap_class(:sm), do: "gap-1"
defp size_gap_class(:md), do: "gap-1.5"
defp size_separator_class(:xs), do: "text-xs"
defp size_separator_class(:sm), do: "text-sm"
defp size_separator_class(:md), do: "text-base"
# Generate title/tooltip text
defp version_title(version, status, is_live, date) do
status_text =
cond do
is_live -> gettext("Published (Live)")
status == "published" -> gettext("Published")
status == "draft" -> gettext("Draft")
status == "archived" -> gettext("Archived")
true -> gettext("Unknown")
end
base = gettext("Version %{version} - %{status}", version: version, status: status_text)
case format_version_date(date) do
nil -> base
formatted_date -> "#{base}\n#{gettext("Created:")} #{formatted_date}"
end
end
# Format ISO 8601 date string for display
defp format_version_date(nil), do: nil
defp format_version_date(""), do: nil
defp format_version_date(date_string) when is_binary(date_string) do
case DateTime.from_iso8601(date_string) do
{:ok, datetime, _offset} ->
Calendar.strftime(datetime, "%Y-%m-%d %H:%M")
_ ->
# Try parsing as NaiveDateTime
case NaiveDateTime.from_iso8601(date_string) do
{:ok, naive_dt} -> Calendar.strftime(naive_dt, "%Y-%m-%d %H:%M")
_ -> nil
end
end
end
defp format_version_date(_), do: nil
end