name: cinder description: Cinder Usage Rules
Cinder Usage Rules
Cinder is a data table component for Phoenix LiveView with Ash Framework integration.
Basic Usage
<Cinder.Table.table resource={MyApp.User} actor={@current_user}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<:col :let={user} field="email" filter>{user.email}</:col>
<:col :let={user} field="created_at" sort>{user.created_at}</:col>
</Cinder.Table.table>
Data Sources
<!-- Resource -->
<Cinder.Table.table resource={MyApp.User} actor={@current_user}>
<!-- Pre-configured query -->
<Cinder.Table.table query={MyApp.User |> Ash.Query.filter(active: true)} actor={@current_user}>
<!-- Custom read action -->
<Cinder.Table.table query={Ash.Query.for_read(MyApp.User, :active_users)} 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 columnsfilter- enables filtering (auto-detects type)sort- enables sortingsearch- includes field in table searchlabel="Custom"- override column header
Filter Configuration
<!-- Auto-detected -->
<:col field="status" filter>Status</:col>
<!-- Specify type -->
<:col field="status" filter={:select}>Status</:col>
<!-- Unified syntax with options -->
<:col field="status" filter={[type: :select, prompt: "All Statuses", options: @statuses]}>Status</:col>
<:col field="price" filter={[type: :number_range, min: 0, max: 1000]}>Price</:col>
<:col field="tags" filter={[type: :multi_select, match_mode: :any, prompt: "Select tags"]}>Tags</:col>
<:col field="active" filter={[type: :boolean, labels: %{true: "Active", false: "Inactive"}]}>Status</:col>
<!-- Custom filter function -->
<:col field="name" filter={[type: :text, fn: &custom_name_filter/2]}>Name</:col>
Sorting Configuration
<!-- Basic sorting -->
<:col field="name" sort>Name</:col>
<!-- Custom sort cycles -->
<:col field="priority" sort={[cycle: [nil, :desc_nils_first, :asc_nils_last]]}>Priority</:col>
Action Columns
<:col :let={user} label="Actions">
<.link patch={~p"/users/#{user.id}/edit"}>Edit</.link>
<button phx-click="delete" phx-value-id={user.id}>Delete</button>
</:col>
Filter-Only Slots
Filter on fields without displaying them as columns:
<Cinder.Table.table resource={MyApp.User} actor={@current_user}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
<!-- Filter-only fields -->
<:filter field="department.name" type="select" options={@departments} />
<:filter field="active" type="boolean" />
<:filter field="created_at" type="date_range" />
</Cinder.Table.table>
Table Configuration
Required
resource={Resource}orquery={query}- data sourceactor={@current_user}- required for Ash authorization
Key Options
theme="modern"- built-in themes: default, modern, retro, futuristic, dark, daisy_ui, flowbite, compact, pastelpage_size={25}- fixed page sizepage_size={[default: 25, options: [10, 25, 50, 100]]}- configurable with dropdownurl_state={@url_state}- enable URL synchronizationrow_click={fn item -> JS.navigate(~p"/path/#{item.id}") end}- row interactivityquery_opts={[timeout: 30_000, authorize?: false]}- Ash query optionsscope={scope}- Ash authorization scopetenant={tenant}- multi-tenancy support
Search Configuration
<!-- Auto-enabled when columns have search attribute -->
<:col :let={user} field="name" search filter>{user.name}</:col>
<!-- Custom search configuration -->
<Cinder.Table.table search={[label: "Search users", placeholder: "Enter name or email"]}>
<!-- Explicitly disable search -->
<Cinder.Table.table search={false}>
Display Options
empty_message="No records found"- custom empty stateloading_message="Loading..."- custom loading stateshow_filters={true}- show/hide filter UIfilters_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
- Options:
- Select (
:select) - enum attributes → dropdown- Options:
options,prompt
- Options:
- Boolean (
:boolean) - boolean fields → true/false radio buttons- Options:
labelsmap withtrue/falsekeys
- Options:
- Date Range (
:date_range) - date/datetime fields → date pickers- Options:
include_time,format
- Options:
- Number Range (
:number_range) - numeric fields → min/max inputs- Options:
min,max,step
- Options:
- Multi-Select (
:multi_select) - array fields → tag-based selection- Options:
options,prompt,match_mode(:any/:all)
- Options:
- Multi-Checkboxes (
:multi_checkboxes) - array fields → checkbox interface- Options:
options,match_mode(:any/:all)
- Options:
- Checkbox (
:checkbox) - single checkbox for "show only X"- Options:
value,label
- Options:
URL State Management
Enable bookmarkable, shareable table states:
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"""
<Cinder.Table.table resource={MyApp.User} actor={@current_user} url_state={@url_state}>
<:col :let={user} field="name" filter sort>{user.name}</:col>
</Cinder.Table.table>
"""
end
end
Custom Filters
1. Configuration
# config/config.exs
config :cinder, :filters, [
slider: MyApp.Filters.Slider,
color_picker: MyApp.Filters.ColorPicker
]
2. Application Setup
# application.ex
def start(_type, _args) do
Cinder.setup() # Registers configured filters
# ... rest of startup
end
3. Filter Module
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
<:col field="price" filter={[type: :slider, min: 0, max: 1000, step: 10]}>Price</:col>
Table Refresh
Refresh table data while preserving state:
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
# config/config.exs
config :cinder, default_theme: "modern"
Per-Table Themes
<Cinder.Table.table theme="dark" resource={MyApp.User}>
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:
{:ok, view, html} = live(conn, ~p"/users")
assert html =~ "Loading..."
assert render_async(view) =~ "John Doe"