diff --git a/assets/css/app.css b/assets/css/app.css index 718bcfe8..2eee35d9 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -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 diff --git a/assets/css/live_table_compat.css b/assets/css/live_table_compat.css index 690b8014..b80eccb1 100644 --- a/assets/css/live_table_compat.css +++ b/assets/css/live_table_compat.css @@ -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 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; diff --git a/assets/js/app.ts b/assets/js/app.ts index 1bf46996..c047581c 100644 --- a/assets/js/app.ts +++ b/assets/js/app.ts @@ -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 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)) diff --git a/config/config.exs b/config/config.exs index a623e4cf..8f10a744 100644 --- a/config/config.exs +++ b/config/config.exs @@ -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, diff --git a/lib/microwaveprop_web/live_table_footer.ex b/lib/microwaveprop_web/live_table_footer.ex new file mode 100644 index 00000000..288ab632 --- /dev/null +++ b/lib/microwaveprop_web/live_table_footer.ex @@ -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""" + + """ + 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