Uh oh!
There was an error while loading. Please reload this page.
Core: table_default sort_bar + DnD + bulk_actions_bar, Reorder/Values/Format utils, blank_to_nil sweep - #548
Merged
Conversation
- New <.empty_state> with three variants (compact / card / featured): the workspace had inline copies plus two module-local versions; lifted to core so projects / catalogue / entities / AI share one component. - New <.sort_selector> — field <.select> + bars-arrow direction toggle in a daisyUI join. Race-free contract: form's phx-change sends only sort_by; arrow's phx-click sends only sort_dir. LV derives the missing field from assigns instead of stale DOM. - <.table_default>: new :card_body slot (fully-custom card content, replaces prescribed card_header + card_fields), card_class attr (string or 1-arity fn per-item), and :above_cards slot that renders inside [data-card-view] (auto-hidden in table mode by the JS hook). - <.sort_header_cell>: cursor-pointer, hover:opacity-80, select-none, loading spinner + opacity dim + pointer-events block, faint chevron-up-down hint on inactive columns. active_direction/2 and sort_header_aria_sort catch-all tolerate atom-or-string sort.dir. - <.admin_page_header>: strip the back-arrow rendering workspace-wide. back and back_click attrs kept as inert no-ops so existing call sites compile without churn. - phoenix_kit_web.ex: auto-import EmptyState + SortSelector for every LV using `use PhoenixKitWeb, :live_view`.
Card grid was capped at 3 columns from lg+ (1024px). On 1536+ viewports the cards became uncomfortably wide (~750px each at 2560 with 3-col). Adding a fourth column at 2xl breakpoint keeps cards ~280-320px wide across every desktop size — readable, never cramped, packs denser on ultrawides.
Two small wrapper components that lift the boilerplate every admin form has been repeating inline: - <.form_section title icon class body_class> — replaces the standard card + card-body + card-title h2 wrapper. Optional Heroicon prefix. - <.form_actions cancel_to submit_label submit_icon submit_class submitting_label> — replaces the right-aligned Cancel link + Submit button row. `submitting_label` defaults to gettext'd "Saving…". Optional :inner_block slot for extra buttons before Cancel/Submit. Both are auto-imported via phoenix_kit_web.ex for every LV using `use PhoenixKitWeb, :live_view`. Consumers in the AI module migrated in the companion phoenix_kit_ai commit; future module forms can adopt them with a one-line swap.
Plain string attr couldn't carry rich content (links, badges). The :subtitle slot lets callers drop a <.pk_link> inline — same rendering shape (small muted text under the title), no extra wrapper. Consumers in phoenix_kit_ai update in the companion commit.
Add `attr :rest, :global` and splat it onto the outer `<section>` so callers can attach `id`, `phx-mounted`, `data-*`, etc. without wrapping the component in an extra div. Needed for the playground response card, which scrolls itself into view via a `phx-mounted` JS dispatch.
table_default gains a `:sort_bar` slot that always renders above the table/cards (both views), replacing the card-only `:above_cards` pattern for sort UI. `<.table_default_body>` now takes `:rest, :global` so consumers can wire `phx-hook="SortableGrid"` and the matching `data-sortable-*` attrs directly onto the `<tbody>` when manual ordering is active. `<.table_default_header_cell>` makes `:inner_block` optional so a drag-handle / select column can render as an empty `<th>` without crashing. sort_selector picks up a `manual_field` attr. When `sort_by` matches, the direction toggle is swapped for a static drag-handle hint — sorting by user-specified order has no asc/desc, and the visual cue tells the operator drag-to-reorder is now the interaction. New `<.bulk_actions_bar>` core component lifts the entities Data Navigator pattern (`PhoenixKitEntities.Web.DataNavigator`) into a reusable primitive: a counter + action-button slot + Clear button. The consumer owns the `MapSet.t()` of selected uuids and wires per-row checkboxes themselves; this component just renders the bar.
Drop the hard-coded card wrapper in favor of a configurable
`wrapper_class` attr that REPLACES (not appends to) the default. The
default still renders the inline-card look used by the entities Data
Navigator, but consumers can pass a sticky / blurred variant to match
the catalogue Items and Categories bars without forking the component.
Collapse the inner card-body div into the flex container so the
wrapper class controls padding too — catalogue's bar is flatter than a
card and shouldn't carry hard-coded `p-3` from inside the component.
Switch the count line to `gettext("%{count} selected", count: @count)`
so translators control word order, and drop the daisyUI horizontal
divider (catalogue doesn't use one; the gap-3 spacing is enough).
Module doc now spells out the two plug-in shapes explicitly:
entities-style inline card and catalogue-style sticky bar.Two small workspace-shared helpers that were duplicated across modules: - `PhoenixKit.Utils.Reorder.reorder/4` lifts the two-phase index-rewrite primitive used for drag-to-reorder list views. Takes a schema, a list of UUIDs in display order, and the position field name; runs the rewrite inside a transaction (negative-then-positive indices so a unique index on the position column wouldn't trip mid-update), with a configurable max-payload cap. Non-UUID entries dedup-filtered via PhoenixKit.Utils.UUID.valid?/1. PhoenixKitProjects.reorder_projects/2 is the reference consumer doing scope checks + audit logging around this primitive; phoenix_kit_ai's reorder_endpoints/1 now delegates here instead of inlining the same pattern. - `PhoenixKit.Utils.Values.blank_to_nil/1` consolidates the `nil | "" -> nil, other -> other` helper that had grown private copies across 7+ modules (activity index, openrouter client, catalogue, project form, etc.). Migrations of the existing copies can land incrementally — this commit only adds the canonical home.
… Reorder
- Activity.Index drops its private blank_to_nil/1 clone and uses
Values.blank_to_nil/1 directly. Same call sites, same behavior.
- Reorder.reorder/4 now returns {:ok, count} (number of rows actually
updated in the positive-write phase) instead of a bare :ok. Lets
consumers like projects/audit-logging preserve the row-count
information they used to compute themselves. AI's reorder_endpoints/1
discards the count and keeps its :ok contract.- New `PhoenixKit.Utils.Format.bytes/2` consolidates eight private format_bytes/format_file_size copies that had grown across the media stack and upload controller. Accepts integer / Decimal / nil with `:decimals`, `:unknown`, and `:base` (1024 vs 1000) options so consumers can preserve their existing visual behavior without forking the algorithm. Six in-tree files now delegate via a one-line `defp` to keep their template call sites unchanged. - `Values.presence/1` is the trim-then-blank-check sibling of `blank_to_nil/1`. The Video component used a private copy with trim; mapper.ex's similarly-named helper was actually `blank_to_nil` (no trim) and migrates to that instead. Real semantic split between the two names is preserved.
6 tasks
ddon pushed a commit
that referenced
this pull request
May 18, 2026
- admin_page_header: drop now-unused Icon import left after the back-button removal; restores `mix compile --warnings-as-errors`. - Utils.Values: add catch-all clauses to blank_to_nil/1 and presence/1 so non-string input (e.g. a list from a `key[]=` query param) passes through / yields nil instead of raising FunctionClauseError. The pre-PR helpers in Activity.Index and video.ex were permissive; this restores that contract for the consolidated versions. - Utils.Reorder: alias PhoenixKit.Utils.UUID instead of the inline fully-qualified call, clearing a credo --strict suggestion. mix precommit clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ddon pushed a commit
that referenced
this pull request
May 18, 2026
mix format on Elixir 1.19 indents `<%= if %>`-block content and separates multi-line defp clauses with blank lines. Reformat the two PR #548 files so `mix precommit` is clean. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ddon pushed a commit
that referenced
this pull request
May 18, 2026
Complete the 1.7.112 entry to cover all unreleased work since v1.7.111: PR #548 (table_default sort/DnD, bulk_actions_bar, empty_state, sort_selector, form_section/form_actions, Reorder/Values/Format utils), PR #544 (MediaBrowser folder management overhaul), PR #545 (V120 document-creator taxonomy), PR #547/#542 (i18n manifests), and this session's post-merge cleanup. Re-dated to 2026-05-18. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Workspace-wide quality lift surfaced by this AI module sweep — most of it is reusable core surface that other modules can adopt incrementally.
<.table_default>extensions:sort_barslot rendered above the toolbar (always visible in both card + table views; replaces the card-only:above_cardspattern for sort UI).<.table_default_body>gains:rest, :globalso consumers can wirephx-hook=\"SortableGrid\"+data-sortable-*attrs directly when manual ordering is active.<.table_default_header_cell>::inner_blockis now optional so drag-handle / select columns can render as empty<th>without a placeholder.<.sort_selector>manual modemanual_fieldattr — whensort_bymatches, the direction toggle is replaced by a static drag-handle hint icon (asc/desc has no meaning for user-specified order).<.form_section>:rest, :globalso consumers can attachid,phx-mounted,data-*without a wrapper div.New
<.bulk_actions_bar>core componentwrapper_classknob lets the consumer pick the inline-card look (default) or the sticky/blurred bar shape catalogue uses on its Categories tab.New utility modules
PhoenixKit.Utils.Reorder.reorder/4— two-phase index-rewrite primitive for drag-to-reorder list views. Schema-agnostic, configurable position field, payload cap (default 500), UUID-filtered against poisoned drops viaPhoenixKit.Utils.UUID.valid?/1. Returns{:ok, count}so consumers can keep audit-log fidelity.PhoenixKit.Utils.Values.blank_to_nil/1+.presence/1—blank_to_nilis the workspace's canonicalnil | \"\" → nilhelper (was duplicated across 7+ modules);presence/1is the trim-then-blank-check sibling for HTML form values / URL query params that may carry stray whitespace.PhoenixKit.Utils.Format.bytes/2—format_bytes/format_file_sizehad grown 8 private copies across the media stack + upload controller. Single helper with:decimals/:unknown/:base(1024 vs 1000) opts so each consumer keeps its existing visual behavior without forking the algorithm.In-tree migrations (clearing the duplicates)
Activity.IndexLV →Values.blank_to_nil/1.Format.bytes/2via one-line delegates so HEEx call sites are unchanged.shared/components/video.ex→Values.presence/1;phoenix_kit_publishing/db_storage/mapper.ex→Values.blank_to_nil/1(consumer-side, separate PR).Test plan
mix precommitclean<.table_default>consumers (AI endpoints, catalogue) work with both card and table views; sort_bar visible in both<.bulk_actions_bar>default style matches entities Data Navigator inline-card lookReorder.reorder/4rewrite is transactional (negative-then-positive write order)