# useCollapsible

## 使用方法

可复用的组合式函数，封装了折叠面板的状态机。支持三种模式：组管理（在 `CollapsibleGroup` 里）、受控（`isOpen` + `onOpenChange`）、不受控（用 `defaultIsOpen` 自己管）。`Collapsible` 组件内部也用它。

```ts
import { useCollapsible } from '@astryx-vue/core/Collapsible'
```

在 `setup()` 里调用，读 `isOpen` / `isEnabled`，或者把 `isOpen` 绑到 `v-show`。hook 只管状态，触发器长什么样由你决定。

```vue
<script setup lang="ts">
import { useCollapsible } from '@astryx-vue/core/Collapsible'

const { isOpen, toggle } = useCollapsible({ isCollapsible: true })
</script>

<template>
  <button type="button" :aria-expanded="isOpen" @click="toggle">
    Details
  </button>
  <div v-show="isOpen">
    Hidden while collapsed.
  </div>
</template>
```

要做受控，就给 `isCollapsible` 传一个对象。`isOpen` 就是那个 model ref，平时是 `v-model:isOpen`：

```ts
const isOpen = ref<boolean | undefined>(false)
const collapsible = useCollapsible({
  isCollapsible: { isOpen, onOpenChange: (next) => { isOpen.value = next } },
})
```

## 建议

| 指引 | 做法 |
| --- | --- |
| Do | 要自己搭折叠组件、又想要 Astryx 的折叠行为时，直接用这个 hook，不必套 `Collapsible` 外壳。 |
| Do | 要做手风琴，就把各项放进 `CollapsibleGroup`，并给每项一个唯一的 `value`。 |
| Don't | 不要指望在 `value` 绑定了组的那一项上，本地的 `isOpen`、`defaultIsOpen`、`onOpenChange` 还会生效；那份状态和通知路径归组所有。 |
| Don't | `useCollapsible` 已经给了开合状态，就不要再自己写一套；组内的协调它已经处理好了。 |

## 示例

用 `useCollapsible` 直接搭一个自定义折叠界面，开关状态完全由 hook 提供。

### useCollapsible — Custom Disclosure

用 useCollapsible 直接搭自定义折叠界面，开关状态完全由 hook 提供。

```vue
<script setup lang="ts">
import { useCollapsible } from '@astryx-vue/core/Collapsible'

const { isOpen, isEnabled, toggle } = useCollapsible({ isCollapsible: true })
</script>

<template>
  <div class="hook-collapsible">
    <button
      type="button"
      class="hook-collapsible__trigger"
      :aria-expanded="isOpen"
      aria-controls="use-collapsible-demo-panel"
      @click="toggle"
    >
      <span>Release checklist</span>
      <span class="hook-collapsible__chevron" :data-open="isOpen || undefined" aria-hidden="true" />
    </button>
    <div v-show="isOpen" id="use-collapsible-demo-panel" class="hook-collapsible__panel">
      <p>Review docs, run visual checks, and confirm keyboard behavior before shipping the component update.</p>
      <p class="hook-collapsible__state">
        isEnabled: {{ isEnabled }} · isOpen: {{ isOpen }}
      </p>
    </div>
  </div>
</template>

<style scoped>
.hook-collapsible {
  box-sizing: border-box;
  width: 22rem;
  max-width: 100%;
  border: 1px solid var(--astryx-color-border, #e5e5e5);
  border-radius: var(--astryx-radius-container, 12px);
  color: var(--astryx-color-text-primary, #171717);
  font-size: 0.875rem;
  overflow: hidden;
}

.hook-collapsible__trigger {
  display: flex;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  width: 100%;
  padding: 12px 16px;
  border: 0;
  background: var(--astryx-color-background-surface, #fff);
  color: inherit;
  font: inherit;
  font-weight: 600;
  text-align: start;
  cursor: pointer;
}

.hook-collapsible__chevron {
  width: 8px;
  height: 8px;
  border-right: 2px solid currentcolor;
  border-bottom: 2px solid currentcolor;
  transform: rotate(45deg);
  transition: transform 150ms ease;
}

.hook-collapsible__chevron[data-open] {
  transform: rotate(225deg);
}

.hook-collapsible__panel {
  padding: 0 16px 16px;
  border-top: 1px solid var(--astryx-color-border, #e5e5e5);
}

.hook-collapsible__panel p {
  margin: 12px 0 0;
  line-height: 1.5;
}

.hook-collapsible__state {
  color: var(--astryx-color-text-secondary, #525252);
  font-family: var(--astryx-font-family-code, ui-monospace, monospace);
  font-size: 0.75rem;
}
</style>
```

## 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `isCollapsible` | `boolean \| CollapsibleConfig \| MaybeRefOrGetter` | `true` | 是否启用折叠。传 `true` 自己管状态且初始展开；传 `false` 关闭折叠；传配置对象表示受控，或自定义默认值。 |
| `value` | `string \| undefined \| MaybeRefOrGetter` | — | 在 `CollapsibleGroup` 里的唯一标识。有值且在组内时，状态由组管理。 |
| `group` | `CollapsibleGroupContextValue \| null` | 自动注入 | 组上下文。不传就从最近的 `CollapsibleGroup` 读取；只有不在那棵树里时才需要手动传。 |

### CollapsibleConfig

| 字段 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `isOpen` | `Ref<boolean \| undefined>` | — | 受控的展开状态，通常就是 `v-model:isOpen` 的 model ref。留空表示不受控。 |
| `defaultIsOpen` | `boolean \| MaybeRefOrGetter` | `true` | `isOpen` 没绑定时，内容初始是否展开。 |
| `onOpenChange` | `(isOpen: boolean) => void` | — | 状态要变化时调用，参数是目标状态。受控时它是请求，不受控时它是通知。 |

## 返回值

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `isEnabled` | `ComputedRef<boolean>` | 折叠能力是否生效。`isCollapsible` 为 `false` 时是 false。 |
| `isOpen` | `ComputedRef<boolean>` | 内容当前是否展开。 |
| `toggle` | `() => void` | 切换展开状态：组内交给组，否则写受控 ref 或内部状态。 |
