# Container Components

Components for grouping and organizing content in OpenTUI.

## Box Component

The primary container component with borders, backgrounds, and layout capabilities.

### Basic Usage

```tsx
// React/Solid
<box>
  <text>Content inside box</text>
</box>

// Core
const box = new BoxRenderable(renderer, {
  id: "container",
})
box.add(child)
```

### Borders

```tsx
<box border>
  Simple border
</box>

<box
  border
  borderStyle="single"    // single | double | rounded | heavy
  borderColor="#FFFFFF"
>
  Styled border
</box>

// Selected sides
<box border={["top", "bottom"]}>
  Top and bottom only
</box>
```

**Border Styles:**

| Style | Appearance |
|-------|------------|
| `single` | `┌─┐│ │└─┘` |
| `double` | `╔═╗║ ║╚═╝` |
| `rounded` | `╭─╮│ │╰─╯` |
| `heavy` | `┏━┓┃ ┃┗━┛` |

### Title

```tsx
<box
  border
  title="Settings"
  titleColor="#FFCC00"          // Title text color (defaults to border color)
  titleAlignment="center"       // left | center | right
  bottomTitle="Press q to quit" // Title text in the bottom border
  bottomTitleAlignment="right"  // left | center | right
>
  Panel content
</box>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `title` | `string` | – | Title text in the top border |
| `titleColor` | `string \| RGBA` | border color | Color of the title text |
| `titleAlignment` | `"left" \| "center" \| "right"` | `"left"` | Top title position |
| `bottomTitle` | `string` | – | Title text in the bottom border |
| `bottomTitleAlignment` | `"left" \| "center" \| "right"` | `"left"` | Bottom title position |

### Background

```tsx
<box backgroundColor="#1a1a2e">
  Dark background
</box>

<box backgroundColor="transparent">
  No background
</box>
```

### Layout

Boxes are flex containers by default:

```tsx
<box
  flexDirection="row"       // row | column | row-reverse | column-reverse
  justifyContent="center"   // flex-start | flex-end | center | space-between | space-around
  alignItems="center"       // flex-start | flex-end | center | stretch | baseline
  gap={2}                   // Space between children
>
  <text>Item 1</text>
  <text>Item 2</text>
</box>
```

### Spacing

```tsx
<box
  padding={2}               // All sides
  paddingTop={1}
  paddingRight={2}
  paddingBottom={1}
  paddingLeft={2}
  paddingX={2}              // Horizontal (left + right)
  paddingY={1}              // Vertical (top + bottom)
  margin={1}
  marginTop={1}
  marginX={2}               // Horizontal (left + right)
  marginY={1}               // Vertical (top + bottom)
>
  Spaced content
</box>
```

### Dimensions

```tsx
<box
  width={40}                // Fixed width
  height={10}               // Fixed height
  width="50%"               // Percentage of parent
  minWidth={20}             // Minimum width
  maxWidth={80}             // Maximum width
  flexGrow={1}              // Grow to fill space
>
  Sized box
</box>
```

### Mouse Events

```tsx
<box
  onMouseDown={(event) => {
    console.log("Clicked at:", event.x, event.y)
  }}
  onMouseUp={(event) => {}}
  onMouseMove={(event) => {}}
>
  Clickable box
</box>
```

### Focusable Boxes

By default, Box elements are not focusable. Set the `focusable` prop to enable focus behavior:

```tsx
// Make a box focusable - it can receive focus via mouse click
<box focusable border>
  <text>Click to focus</text>
</box>

// Controlled focus state
const [focused, setFocused] = useState(false)

<box
  focusable
  focused={focused}
  border
  borderColor={focused ? "#00ff00" : "#888"}
>
  <text>{focused ? "Focused!" : "Not focused"}</text>
</box>
```

When a focusable Box is clicked, focus bubbles up from the click target to the nearest focusable parent. Use `event.preventDefault()` in `onMouseDown` to prevent auto-focus.

## ScrollBox Component

A scrollable container for content that exceeds the viewport.

### Basic Usage

```tsx
// React
<scrollbox height={10}>
  {items.map((item, i) => (
    <text key={i}>{item}</text>
  ))}
</scrollbox>

// Solid
<scrollbox height={10}>
  <For each={items()}>
    {(item) => <text>{item}</text>}
  </For>
</scrollbox>

// Core
const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "list",
  height: 10,
})
items.forEach(item => {
  scrollbox.add(new TextRenderable(renderer, { content: item }))
})
```

### Focus for Keyboard Scrolling

```tsx
<scrollbox focused height={20}>
  {/* Use arrow keys to scroll */}
</scrollbox>
```

### Scrollbar Styling

```tsx
// React
<scrollbox
  style={{
    rootOptions: {
      backgroundColor: "#24283b",
    },
    wrapperOptions: {
      backgroundColor: "#1f2335",
    },
    viewportOptions: {
      backgroundColor: "#1a1b26",
    },
    contentOptions: {
      backgroundColor: "#16161e",
    },
    scrollbarOptions: {
      showArrows: true,
      trackOptions: {
        foregroundColor: "#7aa2f7",
        backgroundColor: "#414868",
      },
    },
  }}
>
  {content}
</scrollbox>
```

### Scroll Position (Core)

```typescript
const scrollbox = new ScrollBoxRenderable(renderer, {
  id: "list",
  height: 20,
})

// Scroll programmatically
scrollbox.scrollTo(0)           // Scroll to top
scrollbox.scrollTo(100)         // Scroll to position
scrollbox.scrollBy(10)          // Scroll relative
scrollbox.scrollToBottom()      // Scroll to end

// Scroll a child into view (nearest alignment)
scrollbox.scrollChildIntoView("child-id")  // Searches descendants by ID
```

`scrollChildIntoView(childId)` scrolls the minimum amount needed to make the identified descendant visible. It mirrors `Element.scrollIntoView({ block: "nearest" })` from the CSSOM View spec. Works with nested descendants and handles both horizontal and vertical scrolling.

## ScrollBar Component

A **standalone** scrollbar (separate from `scrollbox`) with optional arrows, a
draggable thumb, and keyboard navigation. `ScrollBarRenderable` is exported from
`@opentui/core`. Connect it to any content by wiring its size/position props.

```typescript
import { ScrollBarRenderable } from "@opentui/core"

const scrollbar = new ScrollBarRenderable(renderer, {
  id: "scrollbar",
  orientation: "vertical",   // "vertical" | "horizontal"
  showArrows: false,
  scrollSize: 0,             // Total content size
  viewportSize: 0,           // Visible size
  scrollPosition: 0,         // Current position
  onChange: (position) => {
    // Sync your content to the new scroll position
  },
})

// Drive it from your content dimensions, then focus for keyboard control
scrollbar.scrollSize = content.length
scrollbar.viewportSize = visibleRows
scrollbar.scrollPosition = 0
scrollbar.focus()
```

| Prop | Type | Default |
|------|------|---------|
| `orientation` | `"vertical" \| "horizontal"` | – |
| `showArrows` | `boolean` | `false` |
| `arrowOptions` | `ArrowOptions` | – |
| `trackOptions` | `Partial<SliderOptions>` | – |
| `scrollSize` | `number` | `0` |
| `viewportSize` | `number` | `0` |
| `scrollPosition` | `number` | `0` |
| `scrollStep` | `number` | – |
| `onChange` | `(position: number) => void` | – |

When focused: arrows / `hjkl`, `PageUp`/`PageDown`, `Home`/`End`.

> Most apps should use `scrollbox` (which embeds a scrollbar). Reach for
> `ScrollBarRenderable` only when you need a scrollbar decoupled from a
> `scrollbox` viewport.

## Embedded Terminal Component

`EmbeddedTerminalRenderable` is a Core-only Ghostty VT parser and screen. It is
not a process or PTY: write child output into it and send `onData` bytes back to
the child.

```typescript
import { EmbeddedTerminalRenderable } from "@opentui/core"

const terminal = new EmbeddedTerminalRenderable(renderer, {
  width: 80,
  height: 24,
  maxScrollback: 10_000, // Bytes, not lines
  onData(data, source) {
    child.write(data)    // source is "input" or "response"
  },
  onTerminalResize(cols, rows) {
    child.resize(cols, rows)
  },
})

renderer.root.add(terminal)
terminal.write(childOutput) // string | Uint8Array
terminal.focus()
```

Key methods are `write()`, `encodeKey()`, `encodePaste()`, `screen()`,
`invalidate()`, `focus()`, and `blur()`. The renderable handles focused key,
paste, mouse, cursor, scrollback, and selection behavior. `selectable` defaults
to `true`; `cols`/`rows` default to numeric layout dimensions or `80x24`.
Destroy the child and renderable together.

React and Solid do not register an element for this component. Use Core or
register an adapter with `extend()`; catalogue registration alone cannot supply
non-default constructor-only `cols`, `rows`, or `maxScrollback` in Solid.

## Composition Patterns

### Card Component

```tsx
function Card({ title, children }) {
  return (
    <box
      border
      borderStyle="rounded"
      padding={2}
      marginBottom={1}
    >
      {title && (
        <text fg="#00FFFF"><strong>{title}</strong></text>
      )}
      <box marginTop={title ? 1 : 0}>
        {children}
      </box>
    </box>
  )
}
```

### Panel Component

```tsx
function Panel({ title, children, width = 40 }) {
  return (
    <box
      border
      borderStyle="double"
      width={width}
      backgroundColor="#1a1a2e"
    >
      {title && (
        <box
          border={["bottom"]}
          padding={1}
          backgroundColor="#2a2a4e"
        >
          <text><strong>{title}</strong></text>
        </box>
      )}
      <box padding={2}>
        {children}
      </box>
    </box>
  )
}
```

### List Container

```tsx
function List({ items, renderItem }) {
  return (
    <scrollbox height={15} focused>
      {items.map((item, i) => (
        <box
          key={i}
          padding={1}
          backgroundColor={i % 2 === 0 ? "#222" : "#333"}
        >
          {renderItem(item, i)}
        </box>
      ))}
    </scrollbox>
  )
}
```

## Nesting Containers

```tsx
<box flexDirection="column" height="100%">
  {/* Header */}
  <box height={3} border>
    <text>Header</text>
  </box>
  
  {/* Main area with sidebar */}
  <box flexDirection="row" flexGrow={1}>
    <box width={20} border>
      <text>Sidebar</text>
    </box>
    <box flexGrow={1}>
      <scrollbox height="100%">
        {/* Scrollable content */}
      </scrollbox>
    </box>
  </box>
  
  {/* Footer */}
  <box height={1}>
    <text>Footer</text>
  </box>
</box>
```

## Gotchas

### Percentage Dimensions Need Parent Size

```tsx
// WRONG - parent has no explicit size
<box>
  <box width="50%">Won't work</box>
</box>

// CORRECT
<box width="100%">
  <box width="50%">Works</box>
</box>
```

### FlexGrow Needs Sized Parent

```tsx
// WRONG
<box>
  <box flexGrow={1}>Won't grow</box>
</box>

// CORRECT
<box height="100%">
  <box flexGrow={1}>Will grow</box>
</box>
```

### ScrollBox Needs Height

```tsx
// WRONG - no height constraint
<scrollbox>
  {items}
</scrollbox>

// CORRECT
<scrollbox height={20}>
  {items}
</scrollbox>
```

### Borders Add to Size

Borders take up space inside the box:

```tsx
<box width={10} border>
  {/* Inner content area is 8 chars (10 - 2 for borders) */}
</box>
```
