--- name: cinder description: Cinder Usage Rules --- # Cinder Usage Rules Cinder is a data table component for Phoenix LiveView with Ash Framework integration. ## Basic Usage ```heex <:col :let={user} field="name" filter sort>{user.name} <:col :let={user} field="email" filter>{user.email} <:col :let={user} field="created_at" sort>{user.created_at} ``` ## Data Sources ```heex Ash.Query.filter(active: true)} actor={@current_user}> ``` ## Field Notation - **Direct fields**: `field="name"` - **Relationships**: `field="department.name"` - **Embedded resources**: `field="settings__country"` (double underscore) ## Column Configuration ### Data Columns - `field` - required for data columns - `filter` - enables filtering (auto-detects type) - `sort` - enables sorting - `search` - includes field in table search - `label="Custom"` - override column header ### Filter Configuration ```heex <:col field="status" filter>Status <:col field="status" filter={:select}>Status <:col field="status" filter={[type: :select, prompt: "All Statuses", options: @statuses]}>Status <:col field="price" filter={[type: :number_range, min: 0, max: 1000]}>Price <:col field="tags" filter={[type: :multi_select, match_mode: :any, prompt: "Select tags"]}>Tags <:col field="active" filter={[type: :boolean, labels: %{true: "Active", false: "Inactive"}]}>Status <:col field="name" filter={[type: :text, fn: &custom_name_filter/2]}>Name ``` ### Sorting Configuration ```heex <:col field="name" sort>Name <:col field="priority" sort={[cycle: [nil, :desc_nils_first, :asc_nils_last]]}>Priority ``` ### Action Columns ```heex <:col :let={user} label="Actions"> <.link patch={~p"/users/#{user.id}/edit"}>Edit ``` ## Filter-Only Slots Filter on fields without displaying them as columns: ```heex <:col :let={user} field="name" filter sort>{user.name} <:filter field="department.name" type="select" options={@departments} /> <:filter field="active" type="boolean" /> <:filter field="created_at" type="date_range" /> ``` ## Table Configuration ### Required - `resource={Resource}` or `query={query}` - data source - `actor={@current_user}` - required for Ash authorization ### Key Options - `theme="modern"` - built-in themes: default, modern, retro, futuristic, dark, daisy_ui, flowbite, compact, pastel - `page_size={25}` - fixed page size - `page_size={[default: 25, options: [10, 25, 50, 100]]}` - configurable with dropdown - `url_state={@url_state}` - enable URL synchronization - `row_click={fn item -> JS.navigate(~p"/path/#{item.id}") end}` - row interactivity - `query_opts={[timeout: 30_000, authorize?: false]}` - Ash query options - `scope={scope}` - Ash authorization scope - `tenant={tenant}` - multi-tenancy support ### Search Configuration ```heex <:col :let={user} field="name" search filter>{user.name} ``` ### Display Options - `empty_message="No records found"` - custom empty state - `loading_message="Loading..."` - custom loading state - `show_filters={true}` - show/hide filter UI - `filters_label="🔍 Filters"` - customize filter section label ## Built-in Filter Types Auto-detected from Ash resource attributes: - **Text** (`:text`) - string/atom fields → contains/starts_with/ends_with - Options: `operator`, `case_sensitive`, `placeholder` - **Select** (`:select`) - enum attributes → dropdown - Options: `options`, `prompt` - **Boolean** (`:boolean`) - boolean fields → true/false radio buttons - Options: `labels` map with `true`/`false` keys - **Date Range** (`:date_range`) - date/datetime fields → date pickers - Options: `include_time`, `format` - **Number Range** (`:number_range`) - numeric fields → min/max inputs - Options: `min`, `max`, `step` - **Multi-Select** (`:multi_select`) - array fields → tag-based selection - Options: `options`, `prompt`, `match_mode` (:any/:all) - **Multi-Checkboxes** (`:multi_checkboxes`) - array fields → checkbox interface - Options: `options`, `match_mode` (:any/:all) - **Checkbox** (`:checkbox`) - single checkbox for "show only X" - Options: `value`, `label` ## URL State Management Enable bookmarkable, shareable table states: ```elixir defmodule MyAppWeb.UsersLive do use MyAppWeb, :live_view use Cinder.Table.UrlSync def handle_params(params, uri, socket) do socket = Cinder.Table.UrlSync.handle_params(params, uri, socket) {:noreply, socket} end def render(assigns) do ~H""" <:col :let={user} field="name" filter sort>{user.name} """ end end ``` ## Custom Filters ### 1. Configuration ```elixir # config/config.exs config :cinder, :filters, [ slider: MyApp.Filters.Slider, color_picker: MyApp.Filters.ColorPicker ] ``` ### 2. Application Setup ```elixir # application.ex def start(_type, _args) do Cinder.setup() # Registers configured filters # ... rest of startup end ``` ### 3. Filter Module ```elixir defmodule MyApp.Filters.Slider do @behaviour Cinder.Filter use Phoenix.Component @impl true def render(column, current_value, theme, assigns) do # Return HEEx template end @impl true def process(raw_value, column) do # Transform form input to filter value struct %{type: :slider, value: raw_value, operator: :between} end @impl true def validate(filter_value), do: true @impl true def default_options, do: [min: 0, max: 100, step: 1] @impl true def empty?(value), do: is_nil(value) @impl true def build_query(query, field, filter_value) do # Build Ash query filter end end ``` ### 4. Usage ```heex <:col field="price" filter={[type: :slider, min: 0, max: 1000, step: 10]}>Price ``` ## Table Refresh Refresh table data while preserving state: ```elixir import Cinder.Table.Refresh def handle_event("delete", %{"id" => id}, socket) do # ... delete logic ... {:noreply, refresh_table(socket, "table-id")} end # Refresh multiple tables {:noreply, refresh_tables(socket, ["table1", "table2"])} ``` ## Theming ### Global Configuration ```elixir # config/config.exs config :cinder, default_theme: "modern" ``` ### Per-Table Themes ```heex ``` ### Available Themes - `"default"` - minimal styling - `"modern"` - clean, contemporary design - `"dark"` - dark mode styling - `"retro"` - vintage appearance - `"futuristic"` - sci-fi inspired - `"daisy_ui"` - DaisyUI component styles - `"flowbite"` - Flowbite design system - `"compact"` - dense layout - `"pastel"` - soft color palette ## Testing Use `render_async` for data-dependent assertions: ```elixir {:ok, view, html} = live(conn, ~p"/users") assert html =~ "Loading..." assert render_async(view) =~ "John Doe" ```