- Replace gray->cool-steel, blue/indigo->cerulean, red->sweet-salmon, yellow/amber->wheat - Dark sidebar: sidebar/footer use cool-steel-800 bg, text lightened for contrast - ~11,600 color replacements across ~99 files - Update tests for new color class names
165 lines
5.6 KiB
Elixir
165 lines
5.6 KiB
Elixir
defmodule ToweropsWeb.Components.Skeletons do
|
|
@moduledoc """
|
|
Loading skeleton components for placeholder UI during data fetches.
|
|
"""
|
|
use Phoenix.Component
|
|
|
|
# Reusable skeleton block — a single pulsing bar
|
|
defp skel(assigns) do
|
|
assigns = assign_new(assigns, :class, fn -> "" end)
|
|
|
|
~H"""
|
|
<div class={["rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer", @class]}></div>
|
|
"""
|
|
end
|
|
|
|
@doc """
|
|
A pulsing card placeholder matching stat cards.
|
|
"""
|
|
attr :class, :string, default: nil
|
|
|
|
def skeleton_card(assigns) do
|
|
~H"""
|
|
<div class={[
|
|
"animate-pulse rounded-lg border border-cool-steel-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-cool-steel-800/50",
|
|
@class
|
|
]}>
|
|
<.skel class="h-3 w-24" />
|
|
<.skel class="mt-3 h-8 w-16" />
|
|
<.skel class="mt-2 h-3 w-20" />
|
|
</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="overflow-hidden rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50">
|
|
<!-- Header -->
|
|
<div class="border-b border-cool-steel-200 bg-cool-steel-50 px-6 py-3.5 dark:border-white/10 dark:bg-cool-steel-800/75">
|
|
<div class="flex gap-8">
|
|
<div
|
|
:for={_ <- 1..@cols}
|
|
class="h-3 w-20 animate-pulse rounded bg-cool-steel-300 dark:bg-cool-steel-600"
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Rows -->
|
|
<div
|
|
:for={i <- 1..@rows}
|
|
class={[
|
|
"border-b border-cool-steel-100 px-6 py-4 last:border-0 dark:border-white/5",
|
|
rem(i, 2) == 0 && "bg-cool-steel-50/50 dark:bg-cool-steel-800/25"
|
|
]}
|
|
>
|
|
<div class="flex items-center gap-8">
|
|
<div class="h-3 w-24 animate-pulse rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
|
</div>
|
|
<div
|
|
:for={_ <- 2..@cols}
|
|
class="h-3 w-16 animate-pulse rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer"
|
|
>
|
|
</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-cool-steel-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-cool-steel-800/50",
|
|
@class
|
|
]}>
|
|
<div class="flex items-center gap-2">
|
|
<div class="h-5 w-5 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer"></div>
|
|
<div class="h-3 w-20 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer"></div>
|
|
</div>
|
|
<div class="mt-2 h-8 w-16 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
|
</div>
|
|
<div class="mt-1.5 h-2.5 w-14 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
|
</div>
|
|
</div>
|
|
"""
|
|
end
|
|
|
|
@doc """
|
|
Dashboard loading skeleton — mirrors the real dashboard layout.
|
|
"""
|
|
def skeleton_dashboard(assigns) do
|
|
~H"""
|
|
<!-- Stat cards row -->
|
|
<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>
|
|
<!-- Filter bar -->
|
|
<div class="mt-6 flex flex-wrap items-center gap-3">
|
|
<div
|
|
:for={w <- [28, 24, 32, 20]}
|
|
class={[
|
|
"h-9 animate-pulse rounded-lg bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer",
|
|
"w-#{w}"
|
|
]}
|
|
>
|
|
</div>
|
|
</div>
|
|
<!-- Two-column content area -->
|
|
<div class="mt-8 grid gap-8 lg:grid-cols-5">
|
|
<!-- Main column: alerts/activity -->
|
|
<div class="lg:col-span-3">
|
|
<div class="h-5 w-32 animate-pulse rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer mb-4">
|
|
</div>
|
|
<div class="space-y-3">
|
|
<div
|
|
:for={_ <- 1..3}
|
|
class="animate-pulse rounded-lg border border-cool-steel-200 bg-white p-4 dark:border-white/10 dark:bg-cool-steel-800/50"
|
|
>
|
|
<div class="flex items-center gap-3">
|
|
<div class="h-8 w-8 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
|
</div>
|
|
<div class="flex-1">
|
|
<div class="h-3.5 w-40 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
|
</div>
|
|
<div class="mt-2 h-3 w-64 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
|
</div>
|
|
</div>
|
|
<div class="h-3 w-12 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Sidebar column: recent/quick stats -->
|
|
<div class="lg:col-span-2">
|
|
<div class="h-5 w-24 animate-pulse rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer mb-4">
|
|
</div>
|
|
<div class="space-y-3">
|
|
<div
|
|
:for={_ <- 1..4}
|
|
class="animate-pulse rounded-lg border border-cool-steel-200 bg-white p-3 dark:border-white/10 dark:bg-cool-steel-800/50"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<div class="h-3 w-28 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
|
</div>
|
|
<div class="h-5 w-10 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
end
|
|
end
|