Collapse
折叠收纳多组内容的手风琴面板
折叠面板(Collapse)把长内容收进可展开的分节里。标题行直接复用 Cell,因此 icon / label / value 和 Cell 的 leading / subtitle / trailing 一一对应;内容区用 Reanimated 驱动高度过渡(300ms),并让内容绝对定位在独立的测量层里,收起状态下由外层裁掉溢出。
import { Collapse, CollapseItem } from '@skyroc/native-ui';CollapseItem 必须写在 Collapse 内部,否则会直接抛错而不是静默渲染空内容。
基础用法
defaultValue 设置初始展开项。未显式传 name 时,面板的标识就是它在 Collapse 子元素中的序号,所以 defaultValue={0} 展开的是第一项。
import { Collapse, CollapseItem } from '@skyroc/native-ui';
import { View } from 'react-native';
const CONTENT = '代码是写给人看的,只是顺便能被机器执行。折叠面板用来收纳这类长文本,展开时高度会做过渡动画。';
const CollapseBasic = () => {
return (
<View className="bg-muted p-4">
<Collapse defaultValue={0}>
<CollapseItem title="面板一">{CONTENT}</CollapseItem>
<CollapseItem title="面板二">{CONTENT}</CollapseItem>
<CollapseItem
disabled
title="面板三(禁用)"
>
{CONTENT}
</CollapseItem>
</Collapse>
</View>
);
};
export { CollapseBasic };序号只对 Collapse 的直接子元素成立。面板外面套了容器、或需要动态增删面板时,必须显式传 name,否则序号会随渲染结构漂移。
何时使用
- 一屏塞不下的分组信息(FAQ、设置项、订单明细)需要按需展开时使用。
- 同时只允许看一节、需要互斥时用
accordion。 - 只是一行可点的设置项、不需要展开内容时直接用
Cell,不必套Collapse。 - 内容需要盖住整屏、带遮罩时用
Sheet/Popup,折叠面板是就地展开、不脱离文档流的。
手风琴
accordion 下同时只能展开一个面板,展开值从数组收窄为单个面板名,全部收起时是 null(不是 undefined——受控模式下 undefined 会被判定成非受控)。
import { Collapse, CollapseItem, Text } from '@skyroc/native-ui';
import type { CollapseValue } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const CONTENT = '代码是写给人看的,只是顺便能被机器执行。折叠面板用来收纳这类长文本,展开时高度会做过渡动画。';
const CollapseAccordion = () => {
const [accordion, setAccordion] = useState<CollapseValue>(null);
return (
<View className="bg-muted p-4">
<Text className="mb-2 text-sm text-muted-foreground">当前展开:{accordion ?? '无'}</Text>
<Collapse
accordion
value={accordion}
onChange={setAccordion}
>
<CollapseItem
name="one"
title="只能展开一个"
>
{CONTENT}
</CollapseItem>
<CollapseItem
name="two"
title="展开我会收起别人"
>
{CONTENT}
</CollapseItem>
<CollapseItem
name="three"
title="再点一次全部收起"
>
{CONTENT}
</CollapseItem>
</Collapse>
</View>
);
};
export { CollapseAccordion };手风琴模式下 ref.toggleAll() 是空操作。
受控
value + onChange 完全接管展开状态。非手风琴模式下 value 是面板名数组,手风琴模式下是单个面板名或 null。
import { Collapse, CollapseItem, Text } from '@skyroc/native-ui';
import type { CollapseValue } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const CONTENT = '代码是写给人看的,只是顺便能被机器执行。折叠面板用来收纳这类长文本,展开时高度会做过渡动画。';
const CollapseControlled = () => {
const [controlled, setControlled] = useState<CollapseValue>(['a']);
return (
<View className="bg-muted p-4">
<Text className="mb-2 text-sm text-muted-foreground">
当前展开:{Array.isArray(controlled) && controlled.length > 0 ? controlled.join('、') : '无'}
</Text>
<Collapse
value={controlled}
onChange={setControlled}
>
<CollapseItem
name="a"
title="面板 A"
>
{CONTENT}
</CollapseItem>
<CollapseItem
name="b"
title="面板 B"
>
{CONTENT}
</CollapseItem>
</Collapse>
</View>
);
};
export { CollapseControlled };命令式控制
Collapse 的 ref 暴露 toggleAll,CollapseItem 的 ref 暴露 toggle:
| 方法 | 说明 |
|---|---|
toggleAll() | 逐项反转当前状态 |
toggleAll(true / false) | 布尔简写,等价于 { expanded } |
toggleAll({ expanded }) | 全部展开或全部收起 |
toggleAll({ skipDisabled }) | 禁用面板保持原状,不参与本次批量切换 |
item.toggle() | 反转该面板 |
item.toggle(expanded) | 展开或收起该面板 |
import { Button, Collapse, CollapseItem } from '@skyroc/native-ui';
import type { CollapseItemRef, CollapseRef as CollapseGroupRef } from '@skyroc/native-ui';
import { useRef } from 'react';
import { View } from 'react-native';
const CONTENT = '代码是写给人看的,只是顺便能被机器执行。折叠面板用来收纳这类长文本,展开时高度会做过渡动画。';
const CollapseRef = () => {
const groupRef = useRef<CollapseGroupRef>(null);
const firstItemRef = useRef<CollapseItemRef>(null);
function handleToggleAll() {
groupRef.current?.toggleAll();
}
function handleExpandAll() {
groupRef.current?.toggleAll({ expanded: true, skipDisabled: true });
}
function handleToggleFirst() {
firstItemRef.current?.toggle();
}
return (
<View className="bg-muted p-4">
<View className="mb-4 flex-row gap-2">
<Button
color="primary"
size="sm"
variant="solid"
onPress={handleToggleAll}
>
反转全部
</Button>
<Button
color="primary"
size="sm"
variant="outline"
onPress={handleExpandAll}
>
展开全部
</Button>
<Button
color="primary"
size="sm"
variant="outline"
onPress={handleToggleFirst}
>
切换首项
</Button>
</View>
<Collapse ref={groupRef}>
<CollapseItem
ref={firstItemRef}
name="r1"
title="面板一"
>
{CONTENT}
</CollapseItem>
<CollapseItem
name="r2"
title="面板二"
>
{CONTENT}
</CollapseItem>
<CollapseItem
disabled
name="r3"
title="面板三(禁用,展开全部时跳过)"
>
{CONTENT}
</CollapseItem>
</Collapse>
</View>
);
};
export { CollapseRef };toggleAll 依赖面板的挂载注册,只对当前渲染出来的 CollapseItem 生效;item.toggle(true) 对已展开的面板是幂等的,不会重复写入。
尺寸
size 逐项设置,同时决定标题行(Cell)与内容区的规格:
| 尺寸 | 标题行最小高度 | 标题字号 | 箭头 | 内容内边距 | 内容字号 |
|---|---|---|---|---|---|
sm | 40 | text-sm | 11 | px-3 py-2 | text-xs |
md | 48 | text-base | 12 | px-4 py-3 | text-sm |
lg | 56 | text-lg | 14 | px-4 py-3.5 | text-base |
import { Collapse, CollapseItem } from '@skyroc/native-ui';
import { View } from 'react-native';
const CONTENT = '代码是写给人看的,只是顺便能被机器执行。折叠面板用来收纳这类长文本,展开时高度会做过渡动画。';
const CollapseSize = () => {
return (
<View className="bg-muted p-4">
<Collapse>
<CollapseItem
size="sm"
title="Small"
>
{CONTENT}
</CollapseItem>
<CollapseItem
size="md"
title="Medium"
>
{CONTENT}
</CollapseItem>
<CollapseItem
size="lg"
title="Large"
>
{CONTENT}
</CollapseItem>
</Collapse>
</View>
);
};
export { CollapseSize };内容字号只作用于 children 为纯字符串、由组件自动包一层 Text 的情况——RN 不会把文字样式从 View 继承给子级 Text,传自定义节点时需要自己写字号。
自定义标题与只读
icon / label / value 扩展标题行,分别对应 Cell 的左侧图标、标题下方描述与右侧文本。isLink={false} 去掉箭头但仍可展开,readonly 则同时去掉箭头并禁止展开(与 disabled 不同,不会降低透明度)。
import Feather from '@expo/vector-icons/Feather';
import { Collapse, CollapseItem } from '@skyroc/native-ui';
import { View } from 'react-native';
import { withUniwind } from 'uniwind';
const CONTENT = '代码是写给人看的,只是顺便能被机器执行。折叠面板用来收纳这类长文本,展开时高度会做过渡动画。';
/** Feather 不认 className,用 withUniwind 把 `accent-*` 工具类映射到 color 上,避免写死 hex */
const Icon = withUniwind(Feather);
const CollapseCustomized = () => {
return (
<View className="bg-muted p-4">
<Collapse border={false}>
<CollapseItem
icon={
<Icon
colorClassName="accent-primary"
name="wifi"
size={18}
/>
}
label="连接到 skyroc-5G"
title="无线局域网"
value="已连接"
>
{CONTENT}
</CollapseItem>
<CollapseItem
classNames={{ contentText: 'text-primary' }}
icon={
<Icon
colorClassName="accent-primary"
name="bluetooth"
size={18}
/>
}
title="自定义内容颜色"
>
{CONTENT}
</CollapseItem>
<CollapseItem
readonly
title="只读(无箭头,不可展开)"
>
{CONTENT}
</CollapseItem>
</Collapse>
</View>
);
};
export { CollapseCustomized };border={false} 去掉整个 Collapse 的上下外边框;面板之间的分隔线由序号决定(index > 0 的面板画顶部分隔线),不受 border 影响。
懒渲染
lazyRender 默认开启,内容在首次展开时才挂载;收起后内容保留在树中,不会每次展开都重新挂载。需要内容在挂载时就存在(例如内部有需要提前初始化的逻辑)时传 lazyRender={false}。
import { Collapse, CollapseItem, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
const CONTENT = '代码是写给人看的,只是顺便能被机器执行。折叠面板用来收纳这类长文本,展开时高度会做过渡动画。';
const CollapseLazyRender = () => {
return (
<View className="bg-muted p-4">
<Collapse>
<CollapseItem
lazyRender={false}
title="关闭懒渲染"
>
{CONTENT}
</CollapseItem>
<CollapseItem title="默认懒渲染">
<View className="gap-2">
<Text className="text-sm text-foreground">自定义节点内容</Text>
<Text className="text-xs text-muted-foreground">{CONTENT}</Text>
</View>
</CollapseItem>
</Collapse>
</View>
);
};
export { CollapseLazyRender };高度动画依赖内容的 onLayout 测量:懒渲染下首次展开会等测量回调驱动高度;挂载即展开(defaultValue)时直接落位,跳过入场动画。展开状态下内容高度变化也会重新过渡到新高度。
无障碍
标题行是 Cell 渲染的 Pressable,带 accessibilityRole="button",disabled 会映射到 accessibilityState.disabled。展开状态目前没有映射到 accessibilityState.expanded,读屏器不会播报展开 / 收起。
API
Collapse
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| children | 面板列表,通常为 CollapseItem | ReactNode | - |
| value | 受控展开值;手风琴模式下为单个面板名,null 表示全部收起 | CollapseValue | - |
| defaultValue | 非受控初始展开值 | CollapseValue | - |
| onChange | 展开值变化回调 | (value: CollapseValue) => void | - |
| accordion | 手风琴模式,同时只能展开一个面板 | boolean | false |
| border | 是否给整个容器加上下外边框 | boolean | true |
| className | 容器类名 | string | - |
| ref | 命令式控制,暴露 toggleAll | Ref<CollapseRef> | - |
CollapseItem
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| title | 标题文本 | ReactNode | - |
| label | 标题下方的描述 | ReactNode | - |
| value | 标题右侧文本 | ReactNode | - |
| icon | 标题左侧图标 | ReactNode | - |
| children | 面板内容,传字符串时自动包一层 Text | ReactNode | - |
| name | 唯一标识,默认取该面板在 Collapse 直接子元素中的序号;套了容器或动态增删时必须显式传 | string | number | - |
| size | 尺寸,同时决定标题行(Cell)与内容区的规格 | 'sm' | 'md' | 'lg' | 'md' |
| disabled | 禁用,不可展开并整体降低透明度 | boolean | false |
| readonly | 只读,不显示箭头且不可展开,但不降低透明度 | boolean | false |
| isLink | 是否显示右侧箭头,readonly 时强制隐藏 | boolean | true |
| lazyRender | 首次展开时才渲染内容,展开过后保留在树中 | boolean | true |
| className | 根容器类名 | string | - |
| classNames | 各 slot 的类名覆盖 | SlotClassNames<CollapseItemSlots> | - |
| headerClassNames | 标题行各 slot 的类名覆盖;标题行由内部的 Cell 渲染,只能从这里透传 | SlotClassNames<CellSlots> | - |
| ref | 命令式控制,暴露 toggle | Ref<CollapseItemRef> | - |
样式覆盖
| slot | 作用位置 |
|---|---|
root | 单个面板的根容器(含面板间分隔线) |
wrapper | 高度动画层,overflow-hidden 就加在这里 |
content | 内容容器 View,承载内边距与背景 |
contentText | 内容文字,仅在 children 为字符串时生效 |
arrow | 箭头图标的 colorClassName,只接受 accent-* 颜色类 |
内部用于绝对定位测量的那一层不对外开放;标题行的细粒度覆盖走 headerClassNames。
类型
import type {
CollapseItemName,
CollapseItemProps,
CollapseItemRef,
CollapseItemSlots,
CollapseProps,
CollapseRef,
CollapseToggleAllOptions,
CollapseValue
} from '@skyroc/native-ui';CollapseItemName
面板标识,未显式传 name 时为该面板在 Collapse 子元素中的序号。
CollapseValue
展开值:非手风琴模式下是面板名数组,手风琴模式下是单个面板名,null 表示全部收起。
CollapseItemSlots
CollapseItem 可通过 classNames 覆盖的 slot 名称。
SlotClassNames
classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 CollapseItemSlots;cellClassNames 用的是 Cell 页的 CellSlots。
CollapseRef
Collapse 暴露的命令式方法。
| 字段 | 类型 | 说明 |
|---|---|---|
| toggleAll | (options?: CollapseToggleAllOptions | boolean) => void | 批量切换面板;传布尔等价于 { expanded },手风琴模式下为空操作。 |
CollapseToggleAllOptions
toggleAll 的选项。
| 字段 | 类型 | 说明 |
|---|---|---|
| expanded | boolean | 目标状态;缺省时逐项反转当前状态。 |
| skipDisabled | boolean | 为 true 时禁用面板保持原状,不参与本次切换。 |
CollapseItemRef
CollapseItem 暴露的命令式方法。
| 字段 | 类型 | 说明 |
|---|---|---|
| toggle | (expanded?: boolean) => void | 切换该面板;缺省参数时反转当前状态。 |