README.md

# Cinder UI

[![CI](https://github.com/levibuzolic/cinder_ui/actions/workflows/ci.yml/badge.svg)](https://github.com/levibuzolic/cinder_ui/actions/workflows/ci.yml)

[shadcn/ui](https://ui.shadcn.com/docs) components for Phoenix + LiveView.

Cinder UI is a Hex-oriented component library that ports [shadcn/ui](https://ui.shadcn.com/docs) design patterns, classes, tokens, and compositional structure into Elixir function components.

## Installation

### Prerequisites

You need an existing Phoenix 1.7+ project. If you don't have one yet:

```bash
mix phx.new my_app
cd my_app
```

### 1. Set up Tailwind CSS

Cinder UI requires Tailwind CSS v4+. New Phoenix projects generated with `mix phx.new` include Tailwind by default — if yours already has it, skip to [step 2](#2-add-cinder-ui).

Add the Tailwind plugin to your dependencies in `mix.exs`:

```elixir
defp deps do
  [
    {:tailwind, "~> 0.3", runtime: Mix.env() == :dev},
    # ...
  ]
end
```

Configure Tailwind in `config/config.exs`:

```elixir
config :tailwind,
  version: "4.1.12",
  my_app: [
    args: ~w(
      --input=assets/css/app.css
      --output=priv/static/assets/app.css
    ),
    cd: Path.expand("..", __DIR__)
  ]
```

Add the Tailwind watcher in `config/dev.exs`:

```elixir
config :my_app, MyAppWeb.Endpoint,
  watchers: [
    tailwind: {Tailwind, :install_and_run, [:my_app, ~w(--watch)]}
  ]
```

Add Tailwind to the deployment alias in `mix.exs`:

```elixir
defp aliases do
  [
    "assets.deploy": [
      "tailwind my_app --minify",
      "esbuild my_app --minify",
      "phx.digest"
    ]
  ]
end
```

Install Tailwind and fetch dependencies:

```bash
mix deps.get
mix tailwind.install
```

Set up `assets/css/app.css`:

```css
@import "tailwindcss";
```

If your `assets/js/app.js` imports CSS (`import "../css/app.css"`), remove that line — Tailwind handles CSS compilation separately.

### 2. Add Cinder UI

Add the dependency to your `mix.exs`:

```elixir
defp deps do
  [
    {:cinder_ui, "~> 0.1.0"},
    # Optional but recommended — required for the <.icon /> component
    {:lucide_icons, "~> 2.0"},
    # ...
  ]
end
```

Fetch dependencies:

```bash
mix deps.get
```

### 3. Run the installer

Cinder UI includes a Mix task that sets up CSS, JavaScript hooks, and Tailwind plugins automatically:

```bash
mix cinder_ui.install
```

This will:

- Copy `cinder_ui.css` into `assets/css/` (theme variables and dark mode)
- Copy `cinder_ui.js` into `assets/js/` (LiveView hooks for interactive components)
- Update `assets/css/app.css` with:
  - `@source "../../deps/cinder_ui";` — so Tailwind scans component classes
  - `@import "./cinder_ui.css";` — loads theme tokens
- Update `assets/js/app.js` to merge `CinderUIHooks` into your LiveView hooks
- Install the `tailwindcss-animate` npm package

The installer auto-detects your package manager (npm, pnpm, yarn, or bun). To specify one explicitly:

```bash
mix cinder_ui.install --package-manager pnpm
```

To re-run without overwriting customized files:

```bash
mix cinder_ui.install --skip-existing
```

To only (re)copy `cinder_ui.css` and `cinder_ui.js` without patching `app.css`/`app.js`:

```bash
mix cinder_ui.install --skip-patching
```

### 4. Configure your app

Add `use CinderUI` to your app's `html_helpers` in `lib/my_app_web.ex`:

```elixir
defp html_helpers do
  quote do
    use Phoenix.Component
    use CinderUI
    # ...
  end
end
```

Or selectively import only the modules you need:

```elixir
import CinderUI.Components.Actions
import CinderUI.Components.Forms
```

### 5. Start building

Start your Phoenix server:

```bash
mix phx.server
# or from the repo root:
./bin/demo --port 4001
```

Try a component in any template:

```heex
<.button>Click me</.button>
```

## Forms and Validation

`CinderUI.Components.Forms` supports both simple field wrappers and more explicit
field composition for validated LiveView forms.

Basic field usage:

```heex
<.field>
  <:label><.label for="project-name">Project name</.label></:label>
  <.input id="project-name" name="project[name]" />
  <:description>Visible to your team in dashboards and alerts.</:description>
</.field>
```

Explicit composition with validation messaging:

```heex
<.form for={@form} phx-change="validate" phx-submit="save" class="space-y-6">
  <.field invalid={@form[:owner].errors != []}>
    <:label>
      <.label for={@form[:owner].id}>Owner</.label>
    </:label>

    <.field_control>
      <.autocomplete
        id={@form[:owner].id}
        name={@form[:owner].name}
        value={@form[:owner].value}
        aria-label="Owner"
      >
        <:option value="levi" label="Levi Buzolic" description="Engineering" />
        <:option value="mira" label="Mira Chen" description="Design" />
        <:empty>No matching teammates.</:empty>
      </.autocomplete>
    </.field_control>

    <.field_description>Pick the teammate responsible for the workspace.</.field_description>
    <.field_error :for={{msg, _opts} <- @form[:owner].errors}>{msg}</.field_error>
  </.field>

  <.button type="submit">Save</.button>
</.form>
```

Available field helpers:

- `field/1`
- `field_label/1`
- `field_control/1`
- `field_description/1`
- `field_message/1`
- `field_error/1`
- `input/1`
- `select/1`
- `native_select/1`
- `autocomplete/1`

## Interactive Commands

Interactive components that ship with Cinder UI hooks now share a small command
surface through the `cinder-ui:command` custom event. You can drive that
surface directly from LiveView with `CinderUI.JS`.

For overlay-style components that use the shipped hooks, the current baseline
behavior is:

- `Escape` closes dialogs, drawers, popovers, and dropdown menus
- outside click closes popovers and dropdown menus
- dialog and drawer overlay clicks dismiss the overlay
- hook bindings are refreshed after LiveView-driven DOM updates

Supported commands depend on the component, but the common baseline is:

- `open`
- `close`
- `toggle`
- `focus`

Some input-style components also support:

- `clear`

Current limitation:

- popover and dropdown content still use fixed offset positioning classes such
  as `mt-2`; viewport-aware flipping and collision handling are not implemented yet

LiveView example:

```heex
<button phx-click={CinderUI.JS.open(to: "#account-dialog")}>
  Open dialog
</button>

<button phx-click={CinderUI.JS.clear(to: "#owner-autocomplete")}>
  Clear owner
</button>
```

Raw event example:

```js
const dialog = document.querySelector("[data-slot='dialog']")

dialog?.dispatchEvent(
  new CustomEvent("cinder-ui:command", {
    detail: { command: "open" },
  }),
)
```

If you import `CinderUI` from `assets/js/cinder_ui.js`, you can also dispatch
through the helper:

```js
import { CinderUI, CinderUIHooks } from "./cinder_ui"

CinderUI.dispatchCommand(document.querySelector("[data-slot='select']"), "toggle")
```

## Icons (Optional, Recommended)

`CinderUI.Icons.icon/1` dispatches to [`lucide_icons`](https://hex.pm/packages/lucide_icons).

- Cinder UI reads `lucide_icons.icon_names/0` and caches names automatically.
- Both kebab-case and snake_case icon names are supported.

Example:

```heex
<.icon name="chevron-down" class="size-4" />
<.icon name="loader_circle" class="size-4 animate-spin" />
```

If `lucide_icons` is missing and `<.icon />` is used, Cinder UI raises an error.

## Theming and Style Overrides

Cinder UI uses shadcn-style CSS variables (`--background`, `--foreground`, `--primary`, etc.) and dark mode with `.dark`.

### Configure variables (shadcn-style)

```css
:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.145 0 0);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.145 0 0);
  --primary: oklch(0.54 0.22 262);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.97 0 0);
  --secondary-foreground: oklch(0.205 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --accent: oklch(0.97 0 0);
  --accent-foreground: oklch(0.205 0 0);
  --destructive: oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(0.985 0 0);
  --border: oklch(0.922 0 0);
  --input: oklch(0.922 0 0);
  --ring: oklch(0.708 0 0);
  --radius: 0.75rem;
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --primary: oklch(0.72 0.18 262);
  --primary-foreground: oklch(0.205 0 0);
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --accent: oklch(0.269 0 0);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --destructive-foreground: oklch(0.985 0 0);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.556 0 0);
}
```

Set your preferred corner scale by changing `--radius`; component classes (`rounded-md`, `rounded-lg`, etc.) derive from that value through the Tailwind token mapping in `assets/css/cinder_ui.css`.

## API Docs

The docs site is the source of truth for component coverage, examples, and current behavior. Every component module also includes in-source docs and usage examples. Generate docs with:

```bash
mix docs
```

## Feasibility Notes

A subset of shadcn components rely on browser-first stacks (Radix primitives, complex keyboard navigation, chart engines, or heavy client state). For these, Cinder UI provides either progressive LiveView hook behavior or a scaffold component with stable API + styling.

## Attribution and Third-Party Notices

Cinder UI is built on the shoulders of giants, leveraging the awesome work from these projects:

- [shadcn/ui](https://ui.shadcn.com/docs) ([GitHub](https://github.com/shadcn-ui/ui))
- [Tailwind CSS](https://tailwindcss.com/) ([GitHub](https://github.com/tailwindlabs/tailwindcss))
- [tailwindcss-animate](https://github.com/jamiebuilds/tailwindcss-animate)
- [lucide_icons](https://hex.pm/packages/lucide_icons) ([GitHub](https://github.com/zoedsoupe/lucide_icons))
- [Lucide Icons](https://lucide.dev/icons/)

Thank you to the maintainers and contributors.

For third-party license details and links to upstream license texts, see: [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md)

## Contributing

See [`CONTRIBUTING.md`](CONTRIBUTING.md) for contributor setup, quality gates, testing, release workflow, and docs/site build maintenance.

## License

MIT