---
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"
```