install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/cinder" ~/.claude/skills/majiayu000-claude-skill-registry-cinder && rm -rf "$T"
manifest:
skills/data/cinder/SKILL.mdsource content
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:
(double underscore)field="settings__country"
Column Configuration
Data Columns
- required for data columnsfield
- enables filtering (auto-detects type)filter
- enables sortingsort
- includes field in table searchsearch
- override column headerlabel="Custom"
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
orresource={Resource}
- data sourcequery={query}
- required for Ash authorizationactor={@current_user}
Key Options
- built-in themes: default, modern, retro, futuristic, dark, daisy_ui, flowbite, compact, pasteltheme="modern"
- fixed page sizepage_size={25}
- configurable with dropdownpage_size={[default: 25, options: [10, 25, 50, 100]]}
- enable URL synchronizationurl_state={@url_state}
- row interactivityrow_click={fn item -> JS.navigate(~p"/path/#{item.id}") end}
- Ash query optionsquery_opts={[timeout: 30_000, authorize?: false]}
- Ash authorization scopescope={scope}
- multi-tenancy supporttenant={tenant}
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
- custom empty stateempty_message="No records found"
- custom loading stateloading_message="Loading..."
- show/hide filter UIshow_filters={true}
- customize filter section labelfilters_label="🔍 Filters"
Built-in Filter Types
Auto-detected from Ash resource attributes:
- Text (
) - string/atom fields → contains/starts_with/ends_with:text- Options:
,operator
,case_sensitiveplaceholder
- Options:
- Select (
) - enum attributes → dropdown:select- Options:
,optionsprompt
- Options:
- Boolean (
) - boolean fields → true/false radio buttons:boolean- Options:
map withlabels
/true
keysfalse
- Options:
- Date Range (
) - date/datetime fields → date pickers:date_range- Options:
,include_timeformat
- Options:
- Number Range (
) - numeric fields → min/max inputs:number_range- Options:
,min
,maxstep
- Options:
- Multi-Select (
) - array fields → tag-based selection:multi_select- Options:
,options
,prompt
(:any/:all)match_mode
- Options:
- Multi-Checkboxes (
) - array fields → checkbox interface:multi_checkboxes- Options:
,options
(:any/:all)match_mode
- Options:
- Checkbox (
) - single checkbox for "show only X":checkbox- Options:
,valuelabel
- 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
- minimal styling"default"
- clean, contemporary design"modern"
- dark mode styling"dark"
- vintage appearance"retro"
- sci-fi inspired"futuristic"
- DaisyUI component styles"daisy_ui"
- Flowbite design system"flowbite"
- dense layout"compact"
- soft color palette"pastel"
Testing
Use
render_async for data-dependent assertions:
{:ok, view, html} = live(conn, ~p"/users") assert html =~ "Loading..." assert render_async(view) =~ "John Doe"