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

# Collapsible

Collapsible - это область, содержимое которой разворачивается и сворачивается: подробности под кратким описанием, «показать ещё», раздел расширенных настроек. Она всегда отображает своих потомков, а `content` - после них, пока `open`. Значением `open` и триггером, который его переключает (одним из потомков), владеет приложение, и внешний вид тоже задаёт приложение: у `Collapsible` нет собственного триггера, шеврона или подписи. С `reveal` содержимое открывается и закрывается с анимацией: высота его области пружинно растёт от нуля до высоты содержимого и обратно, а всё, что следует за collapsible, смещается вместе с ней.

```rust
use nsh_base::{collapsible, theme};
use nsh_core::{AccessibilityState, prelude::*};

fn storage() -> impl IntoElement {
    let open = signal(false);
    collapsible()
        .open(open)
        .reveal(true)
        .gap_2()
        .child(
            view()
                .role(AccessibilityRole::Button)
                .accessibility_state(move || AccessibilityState {
                    expanded: Some(open.get()),
                    ..AccessibilityState::default()
                })
                .on_press(move || open.update(|o| *o = !*o))
                .flex_row()
                .items_center()
                .justify_between()
                .child(text("Storage"))
                .child(text("›").transition_transform().rotate(move || {
                    if open.get() { deg(90.0) } else { deg(0.0) }
                })),
        )
        .content(text("Photos 120 GB · Apps 54 GB · System 38 GB").text_color(move || theme().muted_foreground))
}
```

```tsx
import { mount, signal, Text, View } from "nsh";
import { Collapsible } from "nsh/base";

function App() {
  const open = signal(false);
  return (
    <Collapsible
      open={open()}
      reveal
      class="gap-2"
      content={<Text class="text-muted-foreground">Photos 120 GB · Apps 54 GB · System 38 GB</Text>}
    >
      <View
        role="button"
        accessibilityState={{ expanded: open() }}
        onPress={() => open.set(!open())}
        class="flex-row items-center justify-between active:opacity-70"
      >
        <Text class="font-medium">Storage</Text>
        <Text class={`transition-transform ${open() ? "rotate-90" : ""}`}>›</Text>
      </View>
    </Collapsible>
  );
}

mount(App);
```

`Collapsible` происходит из [`nsh/base`](https://nsh.dev/translations/ru/modules/nsh-base.md), crate `nsh-base` на Rust. Значение принадлежит приложению: его триггер переключает `open`, а collapsible показывает то значение, которое передаёт приложение. В Rust `collapsible()` создаёт его с сеттером для каждого свойства (`.open(..)`, `.reveal(..)`, `.content(..)`), типизированными методами стиля для области и `.child(..)` для того, что показывается всегда.

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

## Свойства

| Свойство | Rust | Тип | По умолчанию | Описание |
|---|---|---|---|---|
| `open` | `.open(..)` | `boolean` | `false` | Развёрнуто: `content` находится в дереве после потомков. Значением владеет приложение. |
| `reveal` | `.reveal(..)` | `boolean` | `false` | Открывается и закрывается с раскрытием: содержимое находится в области, которая его обрезает, а её высота пружинно растёт от нуля до высоты содержимого при включении `open` и обратно при выключении, с критическим затуханием (около 265 мс, без перерегулирования). То, что следует за collapsible, смещается вместе с ним, а содержимое остаётся в дереве, пока область не закроется. При включённом уменьшении движения открывается и закрывается мгновенно. Область присутствует в раскладке и в закрытом состоянии, нулевой высоты, поэтому `gap` collapsible также учитывается перед ней. |
| `content` | `.content(..)` | `JSX.Element` | нет | То, что разворачивается. Создаётся один раз и сохраняется в свёрнутом состоянии, поэтому то, что внутри, сохраняет своё состояние (текст поля ввода, положение прокрутки, переключатель) от одного открытия к другому. |
| `children` | `.child(..)` | `JSX.Element` | нет | То, что отображается в любом случае: краткое описание, триггер, переключающий `open`. |
| `class`, `style` | методы стиля | `string`, `StyleProp<ViewStyle>` | нет | Область вокруг потомков и содержимого (`gap-2` задаёт отступы между ними). |

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

## События

Collapsible сам ничего не сообщает: триггер принадлежит приложению, и его нажатие (`onPress` у `View` с `role="button"`) - это то место, где приложение переключает `open`.

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

## Части

| Часть | Внутри | Стилизация |
|---|---|---|
| Область | | `class`, `style` |
| Потомки | область, первыми | собственные элементы |
| Область раскрытия | область, после потомков, с `reveal` | нет: она обрезает содержимое и принимает его высоту |
| Содержимое | область, после потомков (в области раскрытия с `reveal`), пока `open` | собственные элементы (`content`) |

Без `reveal` содержимое появляется и исчезает мгновенно, а каждая платформа заново выполняет раскладку области при его появлении или исчезновении. С `reveal` изменение представляет собой анимацию раскладки коммита, который меняет `open` (как `LayoutAnimation.configureNext`, выполняется нативно, поэтому во время движения JavaScript не выполняется): область раскрытия растёт от нуля до высоты содержимого и сжимается обратно, а кадры всего, что сдвигает то же изменение, включая представления после collapsible, смещаются вместе с ней. Вместе с этим работает и переход приложения: шеврон триггера поворачивается (`transition-transform`), а содержимое может плавно появляться с помощью собственных классов.

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

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

- Область не является отдельным элементом для программ чтения с экрана: её потомки и содержимое читаются как есть.
- Триггер сообщает, развёрнута ли область: задайте ему `accessibilityState={{ expanded: open }}` и роль кнопки (`role="button"` у `View`), и программы чтения с экрана объявят «развёрнуто» или «свёрнуто» вместе с его подписью.
- В macOS и вебе триггер является точкой остановки фокуса клавиатуры, когда принимает нажатия: Tab переводит на него фокус, а Space и Return переключают область.

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

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

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

- На каждой платформе содержимое показывается после потомков, пока `open`, и в остальных случаях исключается из раскладки.
- С `reveal` каждая платформа пружинно меняет высоту области на собственном аниматоре (iOS, Android, macOS, Windows, Linux, GPUI и веб) и открывает и закрывает её мгновенно, пока система запрашивает уменьшение движения.
- iOS, Android и Mac Catalyst: нажатие на триггер - это касание. Десктопы (macOS, Windows, Linux, GPUI) и веб: щелчок, а также Space или Return на триггере в фокусе в macOS и вебе.
