defmodule PhiaUi.Components.Tree do @moduledoc """ Tree component for displaying hierarchical data with expandable/collapsible nodes. Uses native HTML `
/` for zero-JavaScript expand/collapse. The browser handles toggle natively — no Phoenix hooks required. ## Sub-components | Function | Element | Purpose | |---------------|------------|--------------------------------------------------| | `tree/1` | `ul` | Root container with `role="tree"` | | `tree_item/1` | `li` | Leaf node or expandable branch | ## Example <.tree id="file-tree"> <.tree_item label="src" expandable={true} expanded={true}> <.tree_item label="components" expandable={true}> <.tree_item label="button.ex" on_click="open_file" value="button.ex" /> <.tree_item label="app.ex" value="app.ex" /> <.tree_item label="mix.exs" value="mix.exs" /> ## Expandable Items Pass `expandable={true}` to render a branch node using `
/`. The `expanded` attribute controls the initial open/closed state server-side. <.tree_item label="src" expandable={true} expanded={true}> <.tree_item label="app.ex" /> ## Leaf Items Leaf nodes can fire Phoenix events via `on_click` and pass a `value`: <.tree_item label="button.ex" on_click="open_file" value="button.ex" /> ## Indentation CSS handles indentation via `
    ` nesting — no `level` prop needed. ## Accessibility - Root `
      ` uses `role="tree"` - Each `
    • ` uses `role="treeitem"` - Expandable items use `aria-expanded` to reflect open/closed state - Nested item groups use `role="group"` - Chevron SVG is presentational (no ARIA label needed) """ use Phoenix.Component import PhiaUi.ClassMerger, only: [cn: 1] # --------------------------------------------------------------------------- # tree/1 # --------------------------------------------------------------------------- attr(:id, :string, required: true, doc: "Unique ID for the root tree element") attr(:class, :string, default: nil, doc: "Additional CSS classes") attr(:rest, :global, doc: "Extra HTML attributes forwarded to the root `
        `") slot(:inner_block, required: true, doc: "`tree_item/1` sub-components") @doc """ Renders the tree root container. The root element is a `
          ` with `role="tree"` for ARIA semantics. """ def tree(assigns) do ~H"""
            <%= render_slot(@inner_block) %>
          """ end # --------------------------------------------------------------------------- # tree_item/1 # --------------------------------------------------------------------------- attr(:label, :string, required: true, doc: "Display text for the tree item") attr(:expandable, :boolean, default: false, doc: "When true, renders a branch node using `
          /`" ) attr(:expanded, :boolean, default: false, doc: "Initial open/closed state for expandable items (server-side)" ) attr(:on_click, :string, default: nil, doc: "Phoenix event name to fire when a leaf item is clicked" ) attr(:value, :string, default: nil, doc: "Value passed as `phx-value-value` on click events" ) attr(:class, :string, default: nil, doc: "Additional CSS classes") slot(:inner_block, doc: "Nested `tree_item/1` components for expandable branches") @doc """ Renders a tree item — either a leaf node or an expandable branch. **Leaf nodes** render a simple `
        • ` with a clickable row. When `on_click` is provided, the row fires a Phoenix event with `phx-value-value={@value}`. **Branch nodes** (when `expandable={true}`) use `
          /` for zero-JavaScript expand/collapse. The `expanded` attribute sets the initial state. Nested items are rendered inside `
            `. """ def tree_item(%{expandable: true} = assigns) do ~H"""
          • <%= @label %>
              <%= render_slot(@inner_block) %>
          • """ end def tree_item(assigns) do ~H"""
          • <%= @label %>
          • """ end end