Skyroc Native UI

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} 展开的是第一项。

CollapseBasic.tsx
Loading…
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 会被判定成非受控)。

CollapseAccordion.tsx
Loading…
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

CollapseControlled.tsx
Loading…
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 暴露 toggleAllCollapseItem 的 ref 暴露 toggle

方法说明
toggleAll()逐项反转当前状态
toggleAll(true / false)布尔简写,等价于 { expanded }
toggleAll({ expanded })全部展开或全部收起
toggleAll({ skipDisabled })禁用面板保持原状,不参与本次批量切换
item.toggle()反转该面板
item.toggle(expanded)展开或收起该面板
CollapseRef.tsx
Loading…
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)与内容区的规格:

尺寸标题行最小高度标题字号箭头内容内边距内容字号
sm40text-sm11px-3 py-2text-xs
md48text-base12px-4 py-3text-sm
lg56text-lg14px-4 py-3.5text-base
CollapseSize.tsx
Loading…
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 不同,不会降低透明度)。

CollapseCustomized.tsx
Loading…
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}

CollapseLazyRender.tsx
Loading…
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面板列表,通常为 CollapseItemReactNode-
value受控展开值;手风琴模式下为单个面板名,null 表示全部收起CollapseValue-
defaultValue非受控初始展开值CollapseValue-
onChange展开值变化回调(value: CollapseValue) => void-
accordion手风琴模式,同时只能展开一个面板booleanfalse
border是否给整个容器加上下外边框booleantrue
className容器类名string-
ref命令式控制,暴露 toggleAllRef<CollapseRef>-

CollapseItem

属性说明类型默认值
title标题文本ReactNode-
label标题下方的描述ReactNode-
value标题右侧文本ReactNode-
icon标题左侧图标ReactNode-
children面板内容,传字符串时自动包一层 TextReactNode-
name唯一标识,默认取该面板在 Collapse 直接子元素中的序号;套了容器或动态增删时必须显式传string | number-
size尺寸,同时决定标题行(Cell)与内容区的规格'sm' | 'md' | 'lg''md'
disabled禁用,不可展开并整体降低透明度booleanfalse
readonly只读,不显示箭头且不可展开,但不降低透明度booleanfalse
isLink是否显示右侧箭头,readonly 时强制隐藏booleantrue
lazyRender首次展开时才渲染内容,展开过后保留在树中booleantrue
className根容器类名string-
classNames各 slot 的类名覆盖SlotClassNames<CollapseItemSlots>-
headerClassNames标题行各 slot 的类名覆盖;标题行由内部的 Cell 渲染,只能从这里透传SlotClassNames<CellSlots>-
ref命令式控制,暴露 toggleRef<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 子元素中的序号。

string | number

CollapseValue

展开值:非手风琴模式下是面板名数组,手风琴模式下是单个面板名,null 表示全部收起。

CollapseItemSlots

CollapseItem 可通过 classNames 覆盖的 slot 名称。

'arrow' | 'content' | 'contentText' | 'root' | 'wrapper'

SlotClassNames

classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 CollapseItemSlots;cellClassNames 用的是 Cell 页的 CellSlots。

Partial<Record<Slots, string>>

CollapseRef

Collapse 暴露的命令式方法。

字段类型说明
toggleAll(options?: CollapseToggleAllOptions | boolean) => void批量切换面板;传布尔等价于 { expanded },手风琴模式下为空操作。

CollapseToggleAllOptions

toggleAll 的选项。

字段类型说明
expandedboolean目标状态;缺省时逐项反转当前状态。
skipDisabledboolean为 true 时禁用面板保持原状,不参与本次切换。

CollapseItemRef

CollapseItem 暴露的命令式方法。

字段类型说明
toggle(expanded?: boolean) => void切换该面板;缺省参数时反转当前状态。