# Pine UI 🌲

[![Hex.pm](https://img.shields.io/hexpm/v/pine_ui_phoenix.svg)](https://hex.pm/packages/pine_ui_phoenix)
[![Docs](https://img.shields.io/badge/hex-docs-blue.svg)](https://hexdocs.pm/pine_ui_phoenix)
[![License](https://img.shields.io/hexpm/l/pine_ui_phoenix.svg)](https://github.com/jamesnjovu/pine_ui_phoenix/blob/main/LICENSE)

**A Tailwind CSS + Alpine.js component library for Phoenix LiveView.** All 42
[Pines UI](https://devdojo.com/pines/docs/introduction) elements as 49 Phoenix function
components — modal, slide-over, dropdown menu, command palette, toast, table, date picker,
tabs, accordion, select, file upload and more. Accessible, dark-mode ready, and usable by AI
coding agents out of the box.

Pines is DevDojo's Alpine.js + Tailwind CSS component library, distributed as HTML you copy
and paste. This package turns it into real Phoenix components with declarative `attr`/`slot`
APIs, compile-time validation, dark mode, and the LiveView plumbing Alpine needs to survive
DOM patching.

```heex
<.card title="Getting started">
  <p>Every Pines element, as a Phoenix component.</p>
  <:footer>
    <.button phx-click={JS.dispatch("pine:open", to: "#docs")}>Read the docs</.button>
  </:footer>
</.card>

<.slide_over id="docs" title="Documentation">…</.slide_over>
```

## Installation

```elixir
def deps do
  [{:pine_ui_phoenix, "~> 0.2.0"}]
end
```

Requires Elixir 1.15+ and `phoenix_live_view >= 0.20.4` — which covers 0.20.x, 1.0, 1.1
and 1.2.

**Three further steps are required.** Skipping them is the difference between working
components and components that render unstyled or flash their contents on load:

1. Point Tailwind at the package, or every class it uses is purged from your production
   build.

   ```js
   // Tailwind v3 — tailwind.config.js
   content: [..., "../deps/pine_ui_phoenix/lib/**/*.ex"]
   ```

   ```css
   /* Tailwind v4 — app.css */
   @source "../../deps/pine_ui_phoenix/lib";
   ```

2. Import the stylesheet, which carries the `[x-cloak]` rule and the marquee keyframes.

   ```css
   @import "../../deps/pine_ui_phoenix/priv/static/pine_ui.css";
   ```

3. Install Alpine and wire up `pineDom()`, so Alpine state survives LiveView patches.

   ```js
   import { pineDom, PineHooks } from "../../deps/pine_ui_phoenix/priv/static/pine_ui.js"

   const liveSocket = new LiveSocket("/live", Socket, {
     params: { _csrf_token: csrfToken },
     hooks: { ...PineHooks },
     dom: pineDom()
   })
   ```

Full details, including which Alpine plugins each component needs, are in the
[installation guide](https://github.com/jamesnjovu/pine_ui_phoenix/blob/main/guides/installation.md).

## Usage

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

A stock Phoenix app already generates its own `<.button>`, `<.table>` and `<.modal>` in
`CoreComponents`, so resolve the clash however suits you:

```elixir
use PineUiPhoenix, except: [:button, :table, :modal]
use PineUiPhoenix, only: [:command, :marquee, :rating]
use PineUiPhoenix, prefix: :pine   # <.pine_button>
```

## Components

All 42 Pines elements, across both the "Tailwind and Alpine" and "Tailwind Only" groups.

| | |
|---|---|
| **Layout** | `card` · `table` · `blockquote` |
| **Forms** | `button` · `text_input` · `textarea` · `checkbox` · `radio_group` · `select` · `select_menu` · `combobox` · `tags_input` · `switch` · `range_slider` · `rating` · `date_picker` · `copy_to_clipboard` |
| **Feedback** | `alert` · `badge` · `banner` · `progress` · `progress_circle` · `progress_steps` · `toast_group` · `toast_trigger` |
| **Navigation** | `accordion` · `tabs` · `breadcrumbs` · `pagination` · `navigation_menu` · `menubar` |
| **Overlays** | `modal` · `full_screen_modal` · `slide_over` · `dropdown_menu` · `context_menu` · `popover` · `hover_card` · `tooltip` · `command` |
| **Media** | `image_gallery` · `carousel` · `video` · `monaco_editor` |
| **Animation** | `marquee` · `retro_grid` · `typing_effect` · `text_animation` |

Fourteen of them need no JavaScript at all.

## What this port changes

It is a port, not a transcription. Where the copy-paste original made sense as HTML but not
as a component, it was rebuilt:

- **The calendar grid is computed in Elixir.** Pines builds it in Alpine; here `Date` handles
  leap years and month lengths, and the days are real `<button>`s in the DOM.
- **`text_animation` needs no GSAP.** The original loads GSAP from a CDN; this splits the
  text server-side and animates with CSS transitions.
- **Alpine state is JSON-encoded, never interpolated.** Building `x-data` by string
  interpolation breaks on apostrophes and allows expression injection.
- **Classes merge rather than concatenate,** so `<.button class="bg-red-500">` actually
  overrides the default background. See [theming](https://github.com/jamesnjovu/pine_ui_phoenix/blob/main/guides/theming.md).
- **Accessibility is wired properly** — real ARIA bindings, unique ids, keyboard navigation,
  and `prefers-reduced-motion`.

## Using it with AI coding agents

The package ships [`usage-rules.md`](https://github.com/jamesnjovu/pine_ui_phoenix/blob/main/usage-rules.md)
and a Claude Code skill, so agents get the component API and the install gotchas without
guessing. If you use [usage_rules](https://hex.pm/packages/usage_rules):

```elixir
{:usage_rules, "~> 1.0", only: [:dev]}
```

```bash
mix usage_rules.sync AGENTS.md pine_ui_phoenix --link-to-folder deps
```

That pulls the rules into your `AGENTS.md`/`CLAUDE.md`. Without it, agents still read
`deps/pine_ui_phoenix/usage-rules.md` directly, and hexdocs publishes
[llms.txt](https://hexdocs.pm/pine_ui_phoenix/llms.txt) for the same purpose.

The component table in those rules is generated from the live `attr`/`slot` metadata and
verified by a test, so it cannot drift from the real API.

## Documentation

- [Installation](https://github.com/jamesnjovu/pine_ui_phoenix/blob/main/guides/installation.md) — Tailwind, Alpine, plugins, hooks
- [LiveView integration](https://github.com/jamesnjovu/pine_ui_phoenix/blob/main/guides/liveview-integration.md) — DOM patching, server-driven state, forms, streams
- [Theming](https://github.com/jamesnjovu/pine_ui_phoenix/blob/main/guides/theming.md) — class merging, dark mode, accent colours
- [Upgrading to v0.2](https://github.com/jamesnjovu/pine_ui_phoenix/blob/main/guides/upgrading.md) — from v0.1.x

Component reference: [hexdocs.pm/pine_ui_phoenix](https://hexdocs.pm/pine_ui_phoenix).

## Upgrading from v0.1.x

Every v0.1.x function name still works and is re-exported as a deprecated delegate, so your
templates keep rendering. Three things do change — the LiveView floor, the palette, and the
removal of `:poison`. See the [upgrade guide](https://github.com/jamesnjovu/pine_ui_phoenix/blob/main/guides/upgrading.md).

## Development

```bash
mix deps.get
mix test              # component + cross-component contract tests
mix lint              # format check, warnings-as-errors, credo
mix dev               # demo app at http://localhost:4444
mix assets.watch      # rebuild the demo's Tailwind CSS
```

`mix dev` runs a demo of every component with its source shown alongside each example. It
serves the *shipped* `priv/static` assets, so it exercises exactly what consumers get.

## Credits

Design and behaviour come from [Pines](https://devdojo.com/pines) by
[DevDojo](https://devdojo.com). This is an independent Phoenix port.

## License

MIT — see [LICENSE](LICENSE).
