---
url: https://nsh.dev/ru/elements/resizable
description: "ResizablePanelGroup и ResizablePanel: панели, расположенные рядом или друг под другом, с разделителем между каждыми двумя из них, который пользователь перетаскивает, чтобы распределить доступное место. Группа отвечает за перетаскивание, ограничения панелей, курсор изменения размера и роль разделителей для программ экранного доступа; размеры, внешний вид разделителей и содержимое панелей определяются приложением."
demo: https://nsh.dev/play/index.html#el-resizable
---

# Resizable

Группа с изменяемыми размерами раскладывает панели рядом или друг над другом, с ручкой на каждой границе между двумя из них: боковая панель рядом с документом, редактор над консолью, список рядом с подробностями. Пользователь перетаскивает ручку, чтобы сдвинуть границу, и каждая панель остаётся в пределах размеров, которые разрешает приложение. `ResizablePanelGroup` отвечает за поведение: перетаскивание, которое выполняется нативно покадрово, распределение места, курсор изменения размера и увеличенную область касания для пальца. Внешний вид определяет приложение: ручка представляет собой полосу без собственной линии, цвета или элемента захвата, а то, что она рисует, является индикатором приложения; панели являются представлениями приложения.

```rust
use nsh_base::{h_resizable, resizable_panel, theme};
use nsh_core::prelude::*;

fn workspace() -> impl IntoElement {
    h_resizable()
        .handle_style(|s| s.bg(move || theme().border()))
        .handle_accessibility_label("Resize the sidebar")
        .on_resize(|sizes: &Vec<Points>| log::info!("sizes: {sizes:?}"))
        .child(
            resizable_panel()
                .size(px(240.0))
                .size_range(px(160.0)..px(480.0))
                .p_4()
                .child(text("Files").text_color(move || theme().muted_foreground())),
        )
        .child(
            resizable_panel()
                .p_4()
                .child(text("Editor").text_color(move || theme().foreground())),
        )
}
```

```tsx
import { mount, signal, Text } from "nsh";
import { ResizablePanel, ResizablePanelGroup } from "nsh/base";

function App() {
  const sidebar = signal(240);
  return (
    <ResizablePanelGroup
      axis="horizontal"
      handleClass="bg-border group-hover:bg-ring group-active:bg-primary"
      handleAccessibilityLabel="Resize the sidebar"
    >
      <ResizablePanel size={sidebar()} minSize={160} maxSize={480} onResize={({ size }) => sidebar.set(size)} class="p-4">
        <Text class="text-muted-foreground">Files</Text>
      </ResizablePanel>
      <ResizablePanel class="p-4">
        <Text class="text-foreground">Editor</Text>
      </ResizablePanel>
    </ResizablePanelGroup>
  );
}

mount(App);
```

`ResizablePanelGroup` и `ResizablePanel` поставляются в [`nsh/base`](https://nsh.dev/translations/ru/modules/nsh-base.md), crate `nsh-base` для Rust. Панели являются прямыми дочерними элементами группы. В Rust `h_resizable()` и `v_resizable()` создают группу по каждой оси (`resizable_panel_group().axis(..)` принимает ось как значение), а `resizable_panel()` создаёт панель, с сеттером для каждого свойства, обработчиком для каждого события, типизированными методами стиля и `.child(..)` для их содержимого; `.size_range(min..max)` задаёт оба ограничения сразу.

<a id="sizes"></a>

## Размеры

`size` панели — это размер, который она хочет получить, в пределах её `minSize` и `maxSize`. Панели без него равномерно делят оставшееся место независимо от содержимого; когда размер есть у каждой панели, оставшееся место делится пропорционально им. Пока пользователь перетаскивает ручку, группа перераспределяет место между панелями в ядре, без участия JS:

- Когда панель перед ручкой растёт, она забирает место у панелей после неё, начиная с ближайшей, каждую вплоть до её `minSize`, пропуская `fixed`.
- Когда она уменьшается ниже своего `minSize`, она забирает место у панелей перед ней.
- Ни одна панель не вырастает больше своего `maxSize`, и сумма панелей никогда не превышает размер группы.

Когда перетаскивание завершается и размеры изменились, группа сообщает размер каждой видимой панели в порядке их расположения (`onResize` у группы), а каждая панель, чей размер изменился, сообщает свой собственный (`onResize` у панели). Панели, которые сдвинуло перетаскивание, сохраняют размеры пользователя, пока приложение не передаст новый `size`; приложение сохраняет их и после жизни группы, передавая сообщённые размеры обратно как `size`, так что раскладка, восстановленная из [хранилища](https://nsh.dev/translations/ru/modules/nsh.md#storage), открывается с теми размерами, которые оставил пользователь. Сообщают только перетаскивания и шаги скринридеров: изменение размера окна, показ или скрытие панели ничего не сообщают.

Когда группа растёт или уменьшается (изменяется размер окна), панели с размером сохраняют его, а панели без размера принимают разницу на себя. Когда группа слишком мала для заданных размеров, панели уменьшаются, за исключением `fixed` (боковая панель, инспектор), и возвращаются к своим размерам, когда место появляется снова. Панель с `visible={false}` не занимает места: остальные делят её пространство, и она возвращается со своим размером, когда снова показывается.

```tsx
<ResizablePanelGroup axis="vertical" handleClass="bg-border">
  <ResizablePanel>
    <Editor />
  </ResizablePanel>
  <ResizablePanel size={180} minSize={120} fixed visible={consoleOpen()}>
    <Console />
  </ResizablePanel>
</ResizablePanelGroup>
```

<a id="handles"></a>

## Ручки

На каждой границе между двумя видимыми панелями есть ручка: полоса толщиной 9 пунктов поперёк оси, центрированная на границе, с курсором изменения размера для своей оси. На сенсорных экранах её область касания выходит ещё на 10 пунктов в каждую сторону, поэтому даже линию толщиной в один пункт легко захватить. Ручка является `group`: то, что она рисует, является индикатором приложения, стилизуемым с помощью `handleClass` / `handleStyle`: 1 пункт поперёк и вся длина границы, поэтому один `bg-border` даёт тонкую линию на любой оси, а `w-[3px]` или `h-[3px]` делает её толще, и её классы `group-hover:` и `group-active:` следуют за указателем над полосой и за нажатием или перетаскиванием. Для чего-то большего, чем линия (захват из точек, значок), `renderHandle` отрисовывает содержимое индикатора, получая от ручки `index`, `axis` и `state`; он перерисовывается при смене состояния, несколько раз за перетаскивание, но не при каждом движении. Анимированный захват является бегунком ручки (`handleThumbClass` / `handleThumbStyle`): одно представление, центрированное на линии, нарисованное вертикально и повёрнутое на четверть оборота на горизонтальной линии, так что один класс подходит для обеих осей; его показывают и анимируют классы `group-hover:` и `group-active:`; ручка остаётся нажатой на всё время перетаскивания, куда бы ни ушёл указатель.

```tsx
<ResizablePanelGroup
  handleClass="w-[3px] items-center justify-center bg-border"
  renderHandle={(handle) => (
    <View class={handle.state === "dragging" ? "h-8 w-1 rounded-full bg-primary" : "h-6 w-1 rounded-full bg-muted-foreground"} />
  )}
>
  {/* panels */}
</ResizablePanelGroup>
```

`disabled` оставляет каждую ручку на месте (заблокированная раскладка).

<a id="props"></a>

## Свойства

### ResizablePanelGroup

| Свойство | Rust | Тип | По умолчанию | Описание |
|---|---|---|---|---|
| `axis` | `h_resizable()`, `v_resizable()`, `.axis(..)` | `"horizontal" \| "vertical"` | `"horizontal"` | Рядом (ручки перетаскиваются влево и вправо) или друг над другом (вверх и вниз). |
| `disabled` | `.disabled(..)` | `boolean` | `false` | Без перетаскивания: ручки остаются на месте. |
| `handleClass`, `handleStyle` | `.handle_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Индикатор каждой ручки, на границе внутри полосы ручки: 1 пункт поперёк оси и вся длина границы, если не указано иное (`w-[3px]`, `h-[3px]`); `group-hover:` и `group-active:` следуют за полосой. |
| `handleThumbClass`, `handleThumbStyle` | `.handle_thumb_class(..)`, `.handle_thumb_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Бегунок каждой ручки: захват, центрированный на линии, нарисованный вертикально, как на вертикальной линии, и повёрнутый на четверть оборота на горизонтальной, так что один класс подходит для обеих осей. Он остаётся тем же представлением, пока ручка находится под указателем, нажата и перетаскивается, поэтому классы `group-hover:` и `group-active:` анимируют его (`h-11 w-[3px] rounded-full bg-muted-foreground opacity-0 scale-y-0 transition group-hover:opacity-40 group-hover:scale-y-[0.45] group-active:opacity-90 group-active:scale-y-100`). |
| `renderHandle` | `.render_handle(\|handle\| ..)` | `(handle: ResizeHandleContext) => JSX.Element` | нет | Содержимое каждого индикатора, перерисовывается при изменении `state` ручки. |
| `handleAccessibilityLabel` | `.handle_accessibility_label(..)` | `string` | нет | Что скринридеры произносят для каждой ручки: что она изменяет. Они меняют её по 10 пунктов за раз. |
| `children` | `.child(..)` | `JSX.Element` | нет | `ResizablePanel`, её прямые дочерние элементы. |
| `class`, `style` | методы стиля | `string`, `StyleProp<ViewStyle>` | `size-full` | Сама группа; она заполняет родителя, если её класс не говорит иначе. |

### ResizablePanel

| Свойство | Rust | Тип | По умолчанию | Описание |
|---|---|---|---|---|
| `size` | `.size(..)` | `number` | нет | Желаемая длина вдоль оси группы, в пунктах, в пределах `minSize` и `maxSize`; новое значение изменяет размер. Без него панель делит оставшееся место. |
| `minSize` | `.min_size(..)`, `.size_range(..)` | `number` | `100` | Наименьший размер панели. |
| `maxSize` | `.max_size(..)`, `.size_range(..)` | `number` | без ограничений | Наибольший размер панели. |
| `fixed` | `.fixed(..)` | `boolean` | `false` | Сохраняет свой размер, когда группа слишком мала для панелей; перетаскивание её собственной ручки всё равно изменяет его. Принимает `size`. |
| `visible` | `.visible(..)` | `boolean` | `true` | Скрытая панель не занимает места, а остальные делят её пространство. |
| `children` | `.child(..)` | `JSX.Element` | нет | Её содержимое. |
| `class`, `style` | методы стиля | `string`, `StyleProp<ViewStyle>` | нет | Собственная поверхность панели. Её размер вдоль оси задаёт группа. |

### ResizeHandleContext

| Поле | Тип | Описание |
|---|---|---|
| `index` | `number` | Граница после `index`-й видимой панели. |
| `axis` | `"horizontal" \| "vertical"` | Ось, вдоль которой она изменяет размер. |
| `state` | `"idle" \| "hovered" \| "pressed" \| "dragging"` | Насколько далеко ушли вместе с ней указатель или палец. |

<a id="events"></a>

## События

| Событие | Rust | Описание |
|---|---|---|
| `onResize` (`ResizablePanelGroup`) | `.on_resize(\|sizes: &Vec<Points>\| ..)` | Перетаскивание завершилось и сдвинуло ручку, либо скринридер её изменил: `sizes`, размер каждой видимой панели в порядке их расположения (слева направо, сверху вниз). |
| `onResize` (`ResizablePanel`) | `.on_resize(\|size: &Points\| ..)` | Перетаскивание завершилось и изменило размер этой панели: `size`, новый размер, который нужно передать обратно как её `size`. |

<a id="parts"></a>

## Части

| Часть | Внутри | Стилизуется через | Состояния |
|---|---|---|---|
| Группа | | `ResizablePanelGroup`: `class`, `style` | |
| Панель | группа | `ResizablePanel`: `class`, `style` | |
| Индикатор ручки | полоса ручки, на границе | `handleClass`, `handleStyle`, `renderHandle` | `group-hover:` под указателем, `group-active:` при нажатии или перетаскивании; `state` в `renderHandle` |
| Бегунок ручки | по центру линии ручки | `handleThumbClass`, `handleThumbStyle` | `group-hover:`, `group-active:` (нажат до конца перетаскивания) |

<a id="accessibility"></a>

## Доступность

- Каждая ручка является для скринридеров регулируемым элементом с меткой `handleAccessibilityLabel`. Её значение — размер предшествующей ей панели в пределах `minSize` и `maxSize` этой панели.
- Смахивание вверх или вниз по ручке в VoiceOver или клавиши громкости в TalkBack сдвигают её на 10 пунктов, как и перетаскивание; группа сообщает об этом через `onResize`. Ручки группы `disabled` остаются на месте.
- Содержимое панелей принадлежит приложению и читается так же, как и везде.

Подробнее о метках, ролях и объявлениях в целом см. в разделе [Доступность](https://nsh.dev/translations/ru/docs/accessibility.md).

<a id="platform-notes"></a>

## Примечания по платформам

- Настольные платформы (macOS, Windows, Linux, GPUI) и веб: указатель над ручкой показывает курсор изменения размера столбца или строки, а перетаскивание мышью или тачпадом сдвигает границу.
- iOS, Android и Mac Catalyst: палец перетаскивает ручку в пределах области касания, которая шире полосы на 10 пунктов с каждой стороны.
- В раскладках справа налево панели и их размеры следуют порядку раскладки.
- Каждая платформа рисует то, что стилизует приложение: индикаторы и панели.
- Разделения в [Dock](https://nsh.dev/translations/ru/elements/dock.md) являются изменяемыми группами: их размер изменяется так же.
