towerops/lib/towerops_web/components/skeletons.ex
Graham McIntie 9e7ee5099d refactor: fix all credo strict issues, format all code, fix broken routes
- Fix broken route paths in dashboard (/discovery, /subscribers/trace, /config-changes)
- Fix insights empty state settings link (org-scoped route)
- Add underscores to all 86400 literals across 6 files
- Alphabetize aliases in search.ex and agent_channel.ex
- Extract changelog parser helper to reduce nesting
- Extract dashboard impact calculation to reduce nesting
- Refactor agent_channel config change detection (pattern match, extract function)
- Combine double Enum.reject into single call in trace.ex
- Fix line length in trace.ex search query
- Replace length/1 > 0 with != [] in trace_live
- Run mix format on all files
2026-02-13 19:34:40 -06:00

109 lines
3.6 KiB
Elixir

defmodule ToweropsWeb.Components.Skeletons do
@moduledoc """
Loading skeleton components for placeholder UI during data fetches.
"""
use Phoenix.Component
@doc """
A pulsing card placeholder.
"""
attr :class, :string, default: nil
def skeleton_card(assigns) do
~H"""
<div class={[
"animate-pulse rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-800/50",
@class
]}>
<div class="h-3 w-24 rounded bg-gray-200 dark:bg-gray-700"></div>
<div class="mt-3 h-8 w-16 rounded bg-gray-200 dark:bg-gray-700"></div>
<div class="mt-2 h-3 w-20 rounded bg-gray-200 dark:bg-gray-700"></div>
</div>
"""
end
@doc """
Table rows with pulsing placeholders.
"""
attr :rows, :integer, default: 5
attr :cols, :integer, default: 4
def skeleton_table(assigns) do
~H"""
<div class="animate-pulse rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50">
<div class="border-b border-gray-200 px-4 py-3 dark:border-white/10">
<div class="flex gap-6">
<div :for={_ <- 1..@cols} class="h-3 w-20 rounded bg-gray-200 dark:bg-gray-700"></div>
</div>
</div>
<div
:for={_ <- 1..@rows}
class="border-b border-gray-100 px-4 py-3 last:border-0 dark:border-white/5"
>
<div class="flex gap-6">
<div :for={_ <- 1..@cols} class="h-3 w-20 rounded bg-gray-200 dark:bg-gray-700"></div>
</div>
</div>
</div>
"""
end
@doc """
A stat card skeleton (number + label placeholder).
"""
attr :class, :string, default: nil
def skeleton_stat(assigns) do
~H"""
<div class={[
"animate-pulse rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-800/50",
@class
]}>
<div class="h-3 w-20 rounded bg-gray-200 dark:bg-gray-700"></div>
<div class="mt-2 h-8 w-12 rounded bg-gray-200 dark:bg-gray-700"></div>
</div>
"""
end
@doc """
Dashboard loading skeleton - full placeholder for the dashboard content.
"""
def skeleton_dashboard(assigns) do
~H"""
<div class="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
<.skeleton_stat :for={_ <- 1..6} />
</div>
<div class="mt-6 flex flex-wrap items-center gap-3">
<div :for={_ <- 1..4} class="h-8 w-28 animate-pulse rounded-lg bg-gray-200 dark:bg-gray-700">
</div>
</div>
<div class="mt-8 grid gap-8 lg:grid-cols-5">
<div class="lg:col-span-3">
<div class="h-5 w-28 animate-pulse rounded bg-gray-200 dark:bg-gray-700 mb-4"></div>
<div class="space-y-3">
<div
:for={_ <- 1..3}
class="animate-pulse rounded-lg border border-gray-200 bg-white p-4 dark:border-white/10 dark:bg-gray-800/50"
>
<div class="h-3 w-32 rounded bg-gray-200 dark:bg-gray-700"></div>
<div class="mt-2 h-4 w-48 rounded bg-gray-200 dark:bg-gray-700"></div>
<div class="mt-2 h-3 w-64 rounded bg-gray-200 dark:bg-gray-700"></div>
</div>
</div>
</div>
<div class="lg:col-span-2">
<div class="h-5 w-20 animate-pulse rounded bg-gray-200 dark:bg-gray-700 mb-4"></div>
<div class="space-y-3">
<div
:for={_ <- 1..3}
class="animate-pulse rounded-lg border border-gray-200 bg-white p-3 dark:border-white/10 dark:bg-gray-800/50"
>
<div class="h-3 w-16 rounded bg-gray-200 dark:bg-gray-700"></div>
<div class="mt-2 h-4 w-40 rounded bg-gray-200 dark:bg-gray-700"></div>
</div>
</div>
</div>
</div>
"""
end
end