# ResizablePanelGroup

> Split panes with drag handles and keyboard resize, built on react-resizable-panels, with RTL handled for horizontal groups.

Source: https://docs.nasaqui.com/components/resizable

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/resizable.json
```

A row or column of panels separated by draggable handles. Users resize the panels with the pointer or the keyboard. It wraps
`react-resizable-panels` and adds Nasaq styling, a focus ring, an optional grip, a localised handle name and RTL support.

## When to use

- Editor, inbox or file-browser layouts where the user decides how much room each area gets.

## When not to use

- A fixed sidebar: use `sidebar-layout`.
- Scrolling content: use `scroll-area`.

## Import

```tsx
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@fadymondy/nasaq/web";
```

## Quick start

```tsx
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@fadymondy/nasaq/web";

export function Split() {
  return (
    <div className="h-72 w-full">
      <ResizablePanelGroup orientation="horizontal">
        <ResizablePanel id="nav" defaultSize="30%" minSize="15%">Navigation</ResizablePanel>
        <ResizableHandle withGrip />
        <ResizablePanel id="main" defaultSize="70%">Content</ResizablePanel>
      </ResizablePanelGroup>
    </div>
  );
}
```

The group fills its parent (`size-full`), so the parent needs a height.

## Anatomy

```
ResizablePanelGroup   data-slot="resizable-group"
  ResizablePanel      data-slot="resizable-panel"
  ResizableHandle     data-slot="resizable-handle"   (role="separator")
    grip              data-slot="resizable-grip"     (when withGrip)
  ResizablePanel
```

## API

### ResizablePanelGroup

Accepts the `Group` props of `react-resizable-panels` (`defaultLayout`, `onLayoutChange`, `onLayoutChanged`, `disabled`, ...).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Side by side, or stacked. |
| `dir` | `"ltr" \| "rtl"` | from the Nasaq provider | Overrides the direction. Only horizontal groups are affected. |

### ResizablePanel

The `Panel` props of the library: `id`, `defaultSize`, `minSize`, `maxSize`, `collapsible`, `collapsedSize`, `onResize`, `panelRef`.
Sizes are numbers (pixels) or strings with a unit (`"30%"`, `"20rem"`). Give panels an `id` when you save the layout.

### ResizableHandle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `withGrip` | `boolean` | `false` | Show a small grip in the middle of the handle. |
| `label` | `string` | "Resize panels" / "تغيير حجم اللوحات" | Accessible name. |
| `disabled` | `boolean` | `false` | Blocks resizing through this handle. |

## Examples

Nested groups and a collapsible panel:

```tsx
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@fadymondy/nasaq/web";

export function Editor() {
  return (
    <div className="h-96">
      <ResizablePanelGroup orientation="horizontal">
        <ResizablePanel id="files" defaultSize="30%" collapsible collapsedSize="0%">الملفات</ResizablePanel>
        <ResizableHandle withGrip />
        <ResizablePanel id="work" defaultSize="70%">
          <ResizablePanelGroup orientation="vertical">
            <ResizablePanel id="editor" defaultSize="65%">المحرر</ResizablePanel>
            <ResizableHandle withGrip />
            <ResizablePanel id="terminal" defaultSize="35%">الطرفية</ResizablePanel>
          </ResizablePanelGroup>
        </ResizablePanel>
      </ResizablePanelGroup>
    </div>
  );
}
```

## Accessibility

The handle is a focusable `role="separator"` with `aria-valuenow`, `aria-valuemin` and `aria-valuemax`.

| Key | Action |
| --- | --- |
| Arrow Left / Right | Moves a horizontal handle 5% (physical direction, also in RTL). |
| Arrow Up / Down | Moves a vertical handle 5%. |
| Home / End | Moves the handle to the limit. |
| Enter | Collapses or expands the panel before a collapsible handle. |

Localise `label` if you name the handles more specifically ("Resize sidebar").

## RTL & i18n

`react-resizable-panels` reads the pointer and the arrow keys in physical coordinates and does not know about `dir`. A
mirrored flex row would therefore drag the wrong way. In a horizontal group inside an RTL provider (or with `dir="rtl"`),
Nasaq lays the group out `ltr` with its children in reverse order and restores `dir="rtl"` on every panel. Result: the first
panel sits on the right, text is right-to-left, and dragging or pressing an arrow moves the handle the way the pointer or key
points. Consequences: the DOM (and tab) order of an RTL horizontal group is reversed, and the children of the group must be
direct `ResizablePanel` / `ResizableHandle` elements (no wrapper components that hide them behind fragments).
Vertical groups need no change.

## Styling & tokens

Uses `bg-border`, `bg-nq-focus`, `outline-nq-focus`, `bg-card`, `text-muted-foreground`. Handle state is on `data-separator`:
`inactive`, `hover`, `active`, `focus`, `disabled`. Add `className` to any part.

## Do / Don't

- Do set `minSize` so a panel cannot vanish; use `collapsible` when it may.
- Do give the group a parent with a height.
- Don't override colours with raw hex.

## Related

- [scroll-area](https://docs.nasaqui.com/components/scroll-area)
- [sidebar-layout](https://docs.nasaqui.com/components/sidebar-layout)

## Lab

https://docs.nasaqui.com/?path=/docs/components-layout-resizable--docs

## Code

### React

```tsx
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@fadymondy/nasaq/web";

export function Split() {
  return (
    <div className="h-72 w-full">
      <ResizablePanelGroup orientation="horizontal">
        <ResizablePanel id="nav" defaultSize="30%" minSize="15%">Navigation</ResizablePanel>
        <ResizableHandle withGrip />
        <ResizablePanel id="main" defaultSize="70%">Content</ResizablePanel>
      </ResizablePanelGroup>
    </div>
  );
}
```

### shadcn

```tsx
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";

export function Split() {
  return (
    <div className="h-72 w-full">
      <ResizablePanelGroup orientation="horizontal">
        <ResizablePanel id="nav" defaultSize="30%" minSize="15%">Navigation</ResizablePanel>
        <ResizableHandle withGrip />
        <ResizablePanel id="main" defaultSize="70%">Content</ResizablePanel>
      </ResizablePanelGroup>
    </div>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqResizableHandle, NqResizablePanel, NqResizablePanelGroup } from "@fadymondy/nasaq/vue";
</script>

<template>
  <div class="h-72 w-full">
    <NqResizablePanelGroup orientation="horizontal">
      <NqResizablePanel id="nav" default-size="30%" min-size="15%">Navigation</NqResizablePanel>
      <NqResizableHandle with-grip />
      <NqResizablePanel id="main" default-size="70%">Content</NqResizablePanel>
    </NqResizablePanelGroup>
  </div>
</template>
```

### Blade

```blade
<div class="h-72 w-full">
    <x-nq::resizable orientation="horizontal">
        <x-nq::resizable.panel id="nav" default-size="30%" min-size="15%">Navigation</x-nq::resizable.panel>
        <x-nq::resizable.handle with-grip />
        <x-nq::resizable.panel id="main" default-size="70%">Content</x-nq::resizable.panel>
    </x-nq::resizable>
</div>
```

### HTML + Alpine

```html
<div class="h-72 w-full">
    <div data-slot="resizable-group" data-orientation="horizontal"
    x-data="nqResizable(JSON.parse('{\u0022orientation\u0022:\u0022horizontal\u0022,\u0022id\u0022:null,\u0022keyboardResizeBy\u0022:10}'))"
    style="display:flex;flex-direction:row;overflow:hidden"
    class="size-full">
    <div data-slot="resizable-panel"
     data-default-size="30%"      data-min-size="15%"                 style="flex:30 1 0px;overflow:hidden"
    id="nav" class="min-w-0">Navigation</div>
        <div data-slot="resizable-handle" role="separator" tabindex="0" data-orientation="horizontal" aria-orientation="vertical"
    aria-label="Resize panels"
    data-separator="inactive"
        style="touch-action:none;user-select:none"
    class="relative flex shrink-0 items-center justify-center bg-border outline-none transition-colors duration-150 ease-nq w-px after:absolute after:-inset-x-1.5 after:inset-y-0 data-[separator=hover]:bg-nq-focus data-[separator=active]:bg-nq-focus focus-visible:bg-nq-focus focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-nq-focus data-[separator=disabled]:pointer-events-none data-[separator=disabled]:opacity-50">
            <div data-slot="resizable-grip" class="z-10 flex shrink-0 items-center justify-center rounded-control border border-border bg-card text-muted-foreground h-6 w-3">
                            <svg data-slot="icon" aria-hidden="true" class="size-2.5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="9" cy="12" r="1"/>
  <circle cx="9" cy="5" r="1"/>
  <circle cx="9" cy="19" r="1"/>
  <circle cx="15" cy="12" r="1"/>
  <circle cx="15" cy="5" r="1"/>
  <circle cx="15" cy="19" r="1"/>
</svg>                    </div>
</div>
        <div data-slot="resizable-panel"
     data-default-size="70%"                     style="flex:70 1 0px;overflow:hidden"
    id="main" class="min-w-0">Content</div>
</div>
</div>
```
