live_table polish: border color, control layout, row clicks, pagination

- Tailwind source(none) wasn't scanning deps, so shadcn tokens
  (border-border, bg-muted, text-foreground, ...) used by live_table
  and sutra_ui weren't generated, leaving `border` to fall back to
  currentcolor — a heavy dark border around every table in light mode.
  Add @source directives for deps/live_table/lib and deps/sutra_ui/lib.
- Drop the hardcoded width:100% on the outer .select wrapper so the
  per-page selector's Tailwind w-24 wins instead of stretching across
  the header and overlapping the search box.
- Make whole rows clickable: global click delegator in app.ts forwards
  tr clicks to the first <a href> in the row (the Actions "View" link),
  skipping inner interactive elements. cursor + hover highlight added.
- Replace the default Prev/Next footer with a daisyUI .join/.btn-based
  pager (MicrowavepropWeb.LiveTableFooter), wired up globally via
  :live_table defaults so it applies to every live_table at once.
This commit is contained in:
Graham McIntire 2026-04-13 08:38:24 -05:00
parent aff27aff7b
commit a8d4d70abe
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
5 changed files with 103 additions and 3 deletions

View file

@ -8,6 +8,8 @@
@source "../css";
@source "../js";
@source "../../lib/microwaveprop_web";
@source "../../deps/live_table/lib";
@source "../../deps/sutra_ui/lib";
/* live_table / sutra_ui compat: maps shadcn tokens live_table uses to
daisyUI base colors and provides component CSS for the SutraUI

View file

@ -31,11 +31,13 @@
--color-ring: var(--color-primary);
}
/* SutraUI Select — used by live_table for the per-page selector. */
/* SutraUI Select used by live_table for the per-page selector.
No width: 100% here Tailwind width utilities (e.g. `w-24`) on the
component must win, otherwise the per-page select overflows the header
row and overlaps the search box. */
*:not(select).select {
position: relative;
display: inline-flex;
width: 100%;
}
.select-trigger {
@ -301,6 +303,26 @@
/* live_table outer container should have a visible surface + border. */
#live-table table { background: var(--color-base-100); }
/* Clickable rows: whole-row click delegates to the first <a> in the row
(typically the Actions column's "View" link). The cursor + hover tint
make it feel interactive; the actual navigation is wired up in app.ts. */
#live-table tbody tr[id] {
cursor: pointer;
transition: background-color 120ms ease;
}
#live-table tbody tr[id]:hover {
background: color-mix(in oklch, var(--color-primary) 6%, transparent);
}
#live-table tbody tr#empty-placeholder {
cursor: default;
}
#live-table tbody tr#empty-placeholder:hover {
background: transparent;
}
/* Filter bar used when filters are configured. */
.filter-bar {
display: flex;

View file

@ -77,6 +77,20 @@ const liveSocket = new LiveSocket("/live", Socket, {
hooks: {...colocatedHooks, PropagationMap, UtcClock, ContactMap, ContactsMap, ElevationProfile, WeatherMap, LocateMe, CopyLink, RoverMap, BeaconMap, BeaconsListMap, CommaNumber},
})
// live_table rows are dead on their own — wire up whole-row clicks to
// trigger the first <a href> inside that row (typically the Actions "View"
// link). Clicks on interactive elements are ignored so inner links/buttons
// still win.
document.addEventListener("click", (e: MouseEvent) => {
const target = e.target as HTMLElement | null
if (!target) return
if (target.closest("a, button, input, select, label, [role='option'], [role='menuitem']")) return
const tr = target.closest("#live-table tbody tr[id]") as HTMLTableRowElement | null
if (!tr || tr.id === "empty-placeholder") return
const link = tr.querySelector("a[href]") as HTMLAnchorElement | null
if (link) link.click()
})
// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))

View file

@ -20,7 +20,10 @@ config :esbuild,
config :live_table,
app: :microwaveprop,
repo: Microwaveprop.Repo,
pubsub: Microwaveprop.PubSub
pubsub: Microwaveprop.PubSub,
defaults: %{
custom_footer: {MicrowavepropWeb.LiveTableFooter, :render}
}
# Configure Elixir's Logger
config :logger, :default_formatter,

View file

@ -0,0 +1,59 @@
defmodule MicrowavepropWeb.LiveTableFooter do
@moduledoc false
use Phoenix.Component
def render(assigns) do
options = assigns.options
table_options = assigns.table_options
paginate? = get_in(options, ["pagination", "paginate?"])
mode = get_in(table_options, [:pagination, :mode])
assigns =
assigns
|> assign(:show_pagination?, paginate? && mode != :infinite_scroll)
|> assign(:page, parse_page(get_in(options, ["pagination", "page"])))
|> assign(:has_next_page?, get_in(options, ["pagination", :has_next_page]) || false)
~H"""
<nav
:if={@show_pagination?}
class="flex items-center justify-between px-4 py-3 sm:px-6"
aria-label="Pagination"
>
<p class="hidden sm:block text-sm opacity-60">
Page <span class="font-medium text-base-content">{@page}</span>
</p>
<div class="join">
<button
type="button"
class="btn btn-sm join-item"
phx-click="sort"
phx-value-page={@page - 1}
disabled={@page == 1}
aria-label="Previous page"
>
«
</button>
<button type="button" class="btn btn-sm join-item btn-active pointer-events-none">
{@page}
</button>
<button
type="button"
class="btn btn-sm join-item"
phx-click="sort"
phx-value-page={@page + 1}
disabled={!@has_next_page?}
aria-label="Next page"
>
»
</button>
</div>
</nav>
"""
end
defp parse_page(page) when is_integer(page), do: page
defp parse_page(page) when is_binary(page), do: String.to_integer(page)
defp parse_page(_), do: 1
end