# Resizable

- Page: https://jiguang132.com/resources/astryx/docs/resizable
- Resource: Astryx (https://jiguang132.com/resources/astryx.md)
- All guides: https://jiguang132.com/resources/astryx/docs.md

Hook-based resizable panel system. useResizable() manages size state and ResizeHandle provides the interactive pill-grip separator. Pass resize props to existing layout components via their resizable prop.

## Installation

```bash
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
```

Requires React 19+, base CSS and a Theme provider. See [Setup](/resources/astryx/docs/getting-started).

## Resizable

Hook-based resizable panel system. useResizable() manages size state and ResizeHandle provides the interactive pill-grip separator. Pass resize props to existing layout components via their resizable prop.

### Usage guidance

- Use percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) when a percentage needs exactly one pixel floor or ceiling. Reuse Table’s pixel() helper; numbers, exact Npx, and pixel(value) remain pixels. State, persistence, callbacks, paint, and ARIA remain resolved pixels.
- Use useResizable() with existing Astryx layout components. Pass the returned props to the resizable prop on LayoutPanel or SideNav.
- Provide an accessible label on each ResizeHandle when multiple handles exist (e.g. "Resize sidebar", "Resize terminal").
- Avoid: Wrap panels in extra container components for resize. The hook-first architecture avoids extra DOM; use it directly on existing components.

### Theming API

```json
{
  "targets": [
    {
      "className": "astryx-resize-handle",
      "visualProps": [
        "direction"
      ]
    },
    {
      "className": "astryx-resize-handle-pill"
    }
  ]
}
```

## useResizable

Hook that manages resize state for one or more panel regions. Returns size, isCollapsed, collapse/expand/resize methods, and props to pass to handles.

| Prop | Type | Default | Description |
|---|---|---|---|
| defaultSize | SizeValue \| ResizableSize | 250 | Initial size. Numbers, exact Npx, and pixel(value) are pixels; exact N% has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one and resolves once. |
| minSize | ResizableSize | 50 | Live minimum. Numbers, exact Npx, and pixel(value) remain pixels; exact N% has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one. |
| maxSize | ResizableSize | Infinity | Live maximum. Numbers, exact Npx, and pixel(value) remain pixels; exact N% has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one. |
| collapsible | boolean | false | Whether the region can collapse to size 0. |
| collapsedSize | number | 40 | Pixel threshold that triggers collapse during drag. |
| snaps | number[] |  | Pixel values to snap to during resize. |
| shrinkOrder | number |  | Cascade priority: lower number shrinks first. |
| autoSaveId | string |  | Key for persisting sizes and collapse state to localStorage. |

### Structured fixed pixels

```tsx
const region = useResizable({
  defaultSize: pixel(333),
  containerRef,
});
```

### One-time structured default

```tsx
import {useRef} from 'react';
import {ResizeHandle, useResizable} from '@astryxdesign/core/Resizable';
import {percent, pixel} from '@astryxdesign/core/Resizable/utils';

const containerRef = useRef<HTMLDivElement>(null);

const region = useResizable({
  defaultSize: percent(40, {min: pixel(333)}),
  containerRef,
});

<ResizeHandle direction="horizontal" resizable={region.props} />;
```

### Live percentage floor

```tsx
const region = useResizable({
  defaultSize: 0,
  minSize: percent(40, {min: pixel(333)}),
  containerRef,
});
```

### Live percentage ceiling

```tsx
const region = useResizable({
  defaultSize: 500,
  maxSize: percent(10, {max: pixel(400)}),
  containerRef,
});
```

## Resize Handle

Draggable separator between panels. Pill-grip design: invisible at rest, visible on hover (0.6 opacity), fully opaque during drag (1.0). Keyboard-accessible.

| Prop | Type | Default | Description |
|---|---|---|---|
| direction | 'horizontal' \| 'vertical' | 'horizontal' | Layout direction: determines cursor and indicator orientation. |
| isReversed | boolean | false | Reverse drag direction. Use when the handle controls a panel on the end/right/bottom side. |
| isDisabled | boolean | false | Whether the handle is interactive. |
| hasDivider | boolean | false | Show a full-length 1px divider line through the handle. Use when adjacent panels share the same background. |
| isAlwaysVisible | boolean | true | Show the pill grip at rest instead of only on hover. Use when discoverability is important. |
| pillPlacement | 'start' \| 'end' \| 'center' \| 'auto' | 'auto' | Which side of the divider the pill sits on. auto = content side (derived from isReversed), flips when collapsed. start = left/top, end = right/bottom, center = centered on divider. |
| label | string | 'Resize handle' | Accessible label for the separator. |
| resizable * | ResizableProps |  | Resize props from useResizable: connects handle to panel. Carries the region's axis ('horizontal' \| 'vertical'), which must match this handle's direction. |
| children | ReactNode |  | Custom handle content. Overrides the default pill + divider. |
| xstyle | StyleXStyles |  | StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value, not an inline style object like style={{}}. |

### Anatomy

- **Handle**: Focusable separator that owns pointer and keyboard resize interaction.
- **Grip pill**: Default visible grip indicator; custom handle content can replace it.
- **Grab zone**: Invisible enlarged pointer region aligned with the grip or divider.

## useResizable

Hook for adding drag-to-resize behavior to layout regions. Supports single-region and multi-region configurations with snap points, collapsible panels, localStorage persistence, and cascade resize ordering.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| defaultSize | SizeValue \| ResizableSize | Initial size. Numbers, exact "Npx", and pixel(value) are pixels. Exact "N%" has no additional pixel bound. percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds one pixel floor or ceiling. A percentage resolves ONCE into pixels — against containerRef when supplied, against the viewport otherwise — and does not track its basis afterwards. The released broad number \| string type remains compatible; runtime validation is authoritative. |
| minSize | ResizableSize | Minimum size. Numbers, exact "Npx", and pixel(value) remain pixels; exact "N%" has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one. Percentage minimums re-resolve when their basis changes and clamp the current pixel selection. |
| maxSize | ResizableSize | Maximum size. Numbers, exact "Npx", and pixel(value) remain pixels; exact "N%" has no additional pixel bound; percent(value, {min: pixel(value)}) or percent(value, {max: pixel(value)}) adds exactly one. Percentage maximums re-resolve when their basis changes and clamp the current pixel selection. |
| containerRef | RefObject<HTMLElement \| null> | The element a percentage is a share of. Caller-owned: the hook never infers one. Omitted, percentages use the viewport, which is the released behaviour. The ref may point at a different element over time — the basis follows it. Until that element is actually laid out (not yet mounted, display:none, detached) percentages use a temporary 1200px basis rather than its zero measurement, and nothing is persisted from it. |
| direction | 'horizontal' \| 'vertical' | Which axis this region resizes along. Selects the container's inline or block content-box size as the percentage basis, and must match the direction given to ResizeHandle. |
| collapsible | boolean | Whether dragging below the collapsed threshold collapses the region to zero. |
| snaps | number[] | Pixel values to snap to during drag. |
| autoSaveId | string | Key for localStorage persistence of size and collapse state across sessions. |
| defaultIsCollapsed | boolean | Initial collapse state (uncontrolled). A persisted entry wins over it. |
| isCollapsed | boolean | Controlled collapse state. collapse(), expand() and a drag past the threshold then report through onCollapseChange instead of changing state internally. |
| onCollapseChange | (isCollapsed: boolean) => void | Called once per collapse state change, via drag or programmatically. |

### Returns

```json
[
  {
    "name": "size",
    "type": "number",
    "description": "Current size in pixels."
  },
  {
    "name": "isCollapsed",
    "type": "boolean",
    "description": "Whether the region is currently collapsed."
  },
  {
    "name": "collapse",
    "type": "() => void",
    "description": "Programmatically collapse the region."
  },
  {
    "name": "expand",
    "type": "() => void",
    "description": "Expand from collapsed state."
  },
  {
    "name": "resize",
    "type": "(size: number) => void",
    "description": "Resize to a specific pixel value."
  },
  {
    "name": "props",
    "type": "ResizableProps",
    "description": "Props to spread on the resizable component or pass to ResizeHandle."
  }
]
```

### Usage guidance

- Use percent(40, {min: pixel(333)}) for a 40% size with a 333px floor, or percent(10, {max: pixel(400)}) for a 10% size with a 400px ceiling. The options argument is required and carries a floor XOR a ceiling.
- A structured default is an initial choice only; a structured minSize or maxSize remains live. State, persistence, callbacks, resize(), paint, and ARIA all use resolved pixel numbers.
- Import percent and Table’s same pixel binding from @astryxdesign/core/Resizable/utils when constructing configuration in a Server Component; the root package exposes one pixel symbol and one percent symbol without collision.
- Use with Layout or AppShell sidebar for resizable navigation panels.
- Set autoSaveId to persist user-chosen sizes across page reloads.
- Avoid: Set minSize too small; content becomes unreadable. Prefer collapsible for panels that can hide entirely.


## Examples

- Resizable: Horizontal resizable split with a draggable handle between two panels.
- Resizable — Collapsible with snap points: A collapsible sidebar with snap points, driven by useResizable. Dragging snaps to preset widths, dragging past the minimum collapses the panel, and the expand method restores it programmatically.

---

Index of all JGuang resources: https://jiguang132.com/llms.txt
