Skyroc Native UI

ActionSheet

从底部弹出的操作面板,展示一组与当前上下文相关的操作

操作面板(ActionSheet)从屏幕底部升起,列出若干与当前上下文相关的操作。组件基于 Sheet 封装(底层是 @gorhom/bottom-sheetBottomSheetModal),负责操作列表、选中态与取消按钮,面板本体的标题栏、遮罩、拖拽条仍由 Sheet 提供。

除了声明式的 <ActionSheet />,还提供命令式的 showActionSheet(),返回一个在选中或取消时结算的 Promise。

import { ActionSheet, closeActionSheet, showActionSheet } from '@skyroc/native-ui';

面板挂在 BottomSheetModal 上,应用根节点需要包一层 GestureHandlerRootViewBottomSheetModalProvider;命令式调用还会把渲染器挂到 PortalHost 上,两者缺一面板都不会出现。

基础用法

actions 定义操作项,每项必须有唯一的 value(它同时是列表 key,因为 name 允许是任意节点)。show + onUpdateShow 控制显示,cancelText 有值时才渲染底部取消按钮。

ActionSheetBasic.tsx
Loading…
import type { ActionSheetAction } from '@skyroc/native-ui';
import { ActionSheet, Button, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const BASIC_ACTIONS: ActionSheetAction[] = [
  { name: '选项一', value: 'one' },
  { name: '选项二', value: 'two' },
  { name: '选项三', value: 'three' }
];

const ActionSheetBasic = () => {
  const [show, setShow] = useState(false);
  const [result, setResult] = useState('尚未操作');
  const [closedCount, setClosedCount] = useState(0);

  function handleSelect(action: ActionSheetAction, index: number) {
    setResult(`选中 ${action.name}(索引 ${index})`);
  }

  return (
    <View className="bg-background">
      <View className="gap-3 p-4">
        <Button
          variant="tonal"
          onPress={() => setShow(true)}
        >
          打开基础面板
        </Button>
        <Text className="text-sm text-muted-foreground">结果:{result}</Text>
        <Text className="text-sm text-muted-foreground">已完成关闭动画:{closedCount} 次</Text>
      </View>

      <ActionSheet
        closeOnClickAction
        actions={BASIC_ACTIONS}
        cancelText="取消"
        defaultValue="two"
        description="默认选中“选项二”"
        show={show}
        title="请选择"
        onCancel={() => setResult('已取消')}
        onClosed={() => setClosedCount(count => count + 1)}
        onSelect={handleSelect}
        onUpdateShow={setShow}
      />
    </View>
  );
};

export { ActionSheetBasic };

onUpdateShow(false) 表示「请求关闭」,此时退场动画才刚开始;要等面板真正卸载再做清理(移除节点、释放资源),监听 onClosed

何时使用

  • 针对某个对象的一组操作(分享、删除、举报),操作数量在 2–6 个之间。
  • 需要「危险操作 + 取消」这类需要用户二次确认的场景,把危险项用 color 标红。
  • 只需要提示信息、不含操作时用 Dialog;面板内容是表单或长列表时直接用 Sheet;仅用于分享渠道时用 ShareSheet

选项状态

单个 ActionSheetAction 支持 subname(描述)、disabledloadingcolor

字段表现
subname在名称下方追加一行弱化描述
disabled降到 50% 不透明度并阻断点击
loading降到 70% 不透明度、阻断点击,并用指示器替换掉名称与描述
color直接写进 style.color,优先级高于选中态的主题色
ActionSheetStatus.tsx
Loading…
import type { ActionSheetAction } from '@skyroc/native-ui';
import { ActionSheet, Button } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const STATUS_ACTIONS: ActionSheetAction[] = [
  { name: '正常选项', subname: 'subname 可补充操作说明', value: 'normal' },
  { disabled: true, name: '禁用选项', subname: 'disabled=true', value: 'disabled' },
  { loading: true, name: '加载中选项', value: 'loading' },
  { color: 'var(--destructive)', name: '危险操作', subname: '使用 destructive 语义色', value: 'danger' }
];

const ActionSheetStatus = () => {
  const [show, setShow] = useState(false);

  return (
    <View className="bg-background">
      <View className="p-4">
        <Button
          variant="tonal"
          onPress={() => setShow(true)}
        >
          查看选项状态
        </Button>
      </View>

      <ActionSheet
        closeOnClickAction
        actions={STATUS_ACTIONS}
        cancelText="取消"
        description="禁用与加载中的选项不可点击"
        show={show}
        title="选项状态"
        onUpdateShow={setShow}
      />
    </View>
  );
};

export { ActionSheetStatus };

loading 的操作项在加载期间不显示文字,只有一个 ActivityIndicator,指示器颜色取自 indicator slot 的 accent-* 类。

按钮变体

variant="button" 把操作项换成带间距的卡片:列表容器加上 gap-3 px-4,每项变成圆角按钮并渲染 action.icon,取消按钮也变成同样的卡片。

ActionSheetButton.tsx
Loading…
import type { ActionSheetAction } from '@skyroc/native-ui';
import { ActionSheet, Button, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const BUTTON_ACTIONS: ActionSheetAction[] = [
  {
    icon: (
      <View className="size-8 items-center justify-center rounded-full bg-success/15">
        <Text className="font-semibold text-success">微</Text>
      </View>
    ),
    name: '微信',
    value: 'wechat'
  },
  {
    icon: (
      <View className="size-8 items-center justify-center rounded-full bg-primary/15">
        <Text className="font-semibold text-primary">链</Text>
      </View>
    ),
    name: '复制链接',
    value: 'link'
  },
  {
    icon: (
      <View className="size-8 items-center justify-center rounded-full bg-warning/15">
        <Text className="font-semibold text-warning">★</Text>
      </View>
    ),
    name: '收藏',
    value: 'star'
  }
];

const ActionSheetButton = () => {
  const [show, setShow] = useState(false);

  return (
    <View className="bg-background">
      <View className="p-4">
        <Button
          variant="tonal"
          onPress={() => setShow(true)}
        >
          打开按钮面板
        </Button>
      </View>

      <ActionSheet
        closeOnClickAction
        actions={BUTTON_ACTIONS}
        cancelText="取消"
        show={show}
        title="分享到"
        variant="button"
        onUpdateShow={setShow}
      />
    </View>
  );
};

export { ActionSheetButton };

两个变体的差异:

维度defaultbutton
操作项外观通栏文字行,行间用 Divider 分隔圆角卡片,靠容器 gap-3 分隔
icon不渲染渲染在名称之前
选中态名称变主题色名称变主题色 + 卡片描边与浅色底
取消按钮与列表之间有一条灰色间隔(cancelGap圆角卡片,无间隔条

button 变体的操作项基态就带了一圈 border-transparent,选中时只换颜色,行高不会跳动。

受控选择

value / onChange 控制当前选中值(非受控时用 defaultValue)。children 是一个 render prop,接收 { action, value, toggle },用于就地展示当前选中项并控制面板开合 —— 不用自己再维护一份显示文本,action.name 就是。

ActionSheetControlled.tsx
Loading…
import type { ActionSheetAction } from '@skyroc/native-ui';
import { ActionSheet, Cell } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const BASIC_ACTIONS: ActionSheetAction[] = [
  { name: '选项一', value: 'one' },
  { name: '选项二', value: 'two' },
  { name: '选项三', value: 'three' }
];

const ActionSheetControlled = () => {
  const [selectedValue, setSelectedValue] = useState('');

  return (
    <View className="bg-background p-4">
      <ActionSheet
        closeOnClickAction
        actions={BASIC_ACTIONS}
        cancelText="取消"
        title="选择城市"
        value={selectedValue}
        onChange={setSelectedValue}
      >
        {args => (
          <Cell
            showArrow
            classNames={{ root: 'rounded-xl border border-border' }}
            title="当前选项"
            trailing={args.action?.name ?? '请选择'}
            onPress={args.toggle}
          />
        )}
      </ActionSheet>
    </View>
  );
};

export { ActionSheetControlled };

选中值和面板显示是两套独立的受控状态:value / onChange 管选中,show / onUpdateShow 管显示,closeOnClickAction 决定选完是否自动关闭(默认 false)。

关闭行为

面板有四条关闭路径,分别由四个属性控制:

属性关闭路径默认值
closeable标题栏右上角关闭按钮true
closeOnBackdropPress点击遮罩true
enablePanDownToClose下拉面板true
closeOnClickAction点击某个操作项false
ActionSheetCloseable.tsx
Loading…
import type { ActionSheetAction } from '@skyroc/native-ui';
import { ActionSheet, Button } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const BASIC_ACTIONS: ActionSheetAction[] = [
  { name: '选项一', value: 'one' },
  { name: '选项二', value: 'two' },
  { name: '选项三', value: 'three' }
];

const ActionSheetCloseable = () => {
  const [show, setShow] = useState(false);

  return (
    <View className="bg-background">
      <View className="p-4">
        <Button
          variant="tonal"
          onPress={() => setShow(true)}
        >
          打开受限面板
        </Button>
      </View>

      <ActionSheet
        closeOnClickAction
        actions={BASIC_ACTIONS}
        cancelText="关闭面板"
        closeOnBackdropPress={false}
        closeable={false}
        enablePanDownToClose={false}
        show={show}
        showHandle={false}
        title="受限关闭"
        onUpdateShow={setShow}
      />
    </View>
  );
};

export { ActionSheetCloseable };

把前三项全部关掉时,务必保留 cancelTextcloseOnClickAction,否则面板将没有任何退出路径。

样式覆盖

classNameclassNames 作用于操作列表这一层,内部 Sheet 面板本体则由 sheetClassName / sheetClassNames 覆盖 —— 两组属性各管一层,不要混用。

slot作用位置
root操作列表容器(含底部安全区内边距)
action单个操作项的 Pressable
actionName操作项名称文字
actionSubname操作项描述文字
indicatorloading 指示器的 colorClassName,只接受 accent-*
cancelGap取消按钮上方的灰色间隔条(仅 default 变体)
cancel取消按钮的 Pressable
cancelName取消按钮文字
ActionSheetStyles.tsx
Loading…
import type { ActionSheetAction } from '@skyroc/native-ui';
import { ActionSheet, Button } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const BASIC_ACTIONS: ActionSheetAction[] = [
  { name: '选项一', value: 'one' },
  { name: '选项二', value: 'two' },
  { name: '选项三', value: 'three' }
];

const ActionSheetStyles = () => {
  const [show, setShow] = useState(false);

  return (
    <View className="bg-background">
      <View className="p-4">
        <Button
          variant="tonal"
          onPress={() => setShow(true)}
        >
          打开自定义面板
        </Button>
      </View>

      <ActionSheet
        closeOnClickAction
        actions={BASIC_ACTIONS}
        cancelText="再想想"
        classNames={{
          actionName: 'font-medium',
          cancelName: 'text-primary',
          root: 'bg-primary/5'
        }}
        sheetClassNames={{ title: 'text-primary' }}
        show={show}
        title="自定义样式"
        onUpdateShow={setShow}
      />
    </View>
  );
};

export { ActionSheetStyles };

优先级从低到高为:变体样式 → classNames.action → 单个 action.classNameclassName 排在 classNames.root 之后,同样是后者优先级更高。

命令式调用

showActionSheet(options) 直接弹出面板并返回 Promise<ActionSheetResult | null>:选中时 resolve { action, index },取消、点遮罩、下拉关闭时 resolve nullcloseActionSheet() 从外部关闭当前面板,等待中的 Promise 会按取消结算而不是永远挂起。

ActionSheetImperative.tsx
Loading…
import type { ActionSheetAction } from '@skyroc/native-ui';
import { Button, Text, closeActionSheet, showActionSheet } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';

const BASIC_ACTIONS: ActionSheetAction[] = [
  { name: '选项一', value: 'one' },
  { name: '选项二', value: 'two' },
  { name: '选项三', value: 'three' }
];

const ActionSheetImperative = () => {
  const [lastResult, setLastResult] = useState('尚未调用');

  async function handleImperative() {
    const result = await showActionSheet({
      actions: BASIC_ACTIONS,
      cancelText: '取消',
      description: '选中返回 action 与 index,取消返回 null',
      title: '命令式调用'
    });

    setLastResult(result ? `选中 ${result.action.value}(索引 ${result.index})` : '已取消');
  }

  function handleAutoClose() {
    showActionSheet({
      actions: BASIC_ACTIONS,
      description: '面板将在两秒后由 closeActionSheet 关闭',
      title: '外部关闭'
    });

    setTimeout(closeActionSheet, 2000);
  }

  return (
    <View className="gap-3 bg-background p-4">
      <Text className="text-sm text-muted-foreground">上次结果:{lastResult}</Text>
      <View className="flex-row flex-wrap gap-3">
        <Button
          className="min-w-32 flex-1"
          variant="tonal"
          onPress={handleImperative}
        >
          等待选择结果
        </Button>
        <Button
          className="min-w-32 flex-1"
          variant="outline"
          onPress={handleAutoClose}
        >
          两秒后关闭
        </Button>
      </View>
    </View>
  );
};

export { ActionSheetImperative };

命令式调用的几条约定:

  • 展示状态由内部渲染器托管,所以 show / defaultShow / onUpdateShow / onClosed / children / value 不开放,预选值请传 defaultValue
  • closeOnClickAction 默认为 true(声明式是 false):没有外部状态承接选中值,点完不关面板会一直挂在那里。
  • 同一时刻只显示一个面板。再次调用会顶替当前面板,被顶替的那条按取消结算,它的 Promise resolve 为 null
  • 结算恰好触发一次:onSelect / onCancel / callback 与 Promise 走同一条路径,选中一项不会被记成「选中 + 取消」两次。

无障碍

操作项与取消按钮都是 Pressable,禁用与加载中的项通过 disabled 阻断点击。面板本身的语义(标题、关闭按钮)由内部 Sheet 提供。名称传自定义节点时,建议保证节点内含可朗读的文本。

API

ActionSheet

属性说明类型默认值
actions操作项列表ActionSheetAction[][]
variant展示变体:default 为文字列表,button 为按钮卡片'default' | 'button''default'
title面板标题string-
description描述信息,显示在标题下方string-
cancelText取消按钮文字,不设置则不显示取消按钮string-
show是否显示面板(受控)boolean-
defaultShow非受控模式默认是否显示booleanfalse
value受控选中值string-
defaultValue非受控模式默认选中值string''
closeOnClickAction点击选项后是否自动关闭;命令式调用时默认为 truebooleanfalse
closeable是否显示标题栏的关闭按钮booleantrue
closeOnBackdropPress是否允许点击遮罩关闭booleantrue
enablePanDownToClose是否允许下拉关闭booleantrue
showHandle是否显示顶部拖拽指示条booleantrue
childrenRender prop,接收 { action, value, toggle } 用于展示当前选中项并控制面板(args: ActionSheetRenderArgs) => ReactNode-
onSelect选项点击回调(action: ActionSheetAction, index: number) => void-
onChange选中值变化回调(value: string) => void-
onCancel取消按钮点击回调() => void-
onUpdateShow显示状态变化回调,请求关闭时触发(show: boolean) => void-
onClosed退场动画播放完毕、面板真正卸载后触发() => void-
className操作列表容器的类名string-
classNames覆盖操作列表各 slot 的类名SlotClassNames<ActionSheetSlots>-
sheetClassName覆盖内部 Sheet 面板本体的类名string-
sheetClassNames覆盖内部 Sheet 各 slot 的类名SlotClassNames<SheetSlots>-
ref底层 BottomSheetModal 的实例引用,用于 snapToIndex / expand / collapse 等命令式操作Ref<BottomSheetModal>-

showActionSheet

function showActionSheet(options: ActionSheetOptions): Promise<ActionSheetResult | null>;

命令式显示操作面板。选中 resolve ActionSheetResult,取消 / 遮罩关闭 / 被顶替 resolve null

closeActionSheet

function closeActionSheet(): void;

关闭当前面板,按取消结算。没有面板在显示时调用无副作用。

类型

import type {
  ActionSheetAction,
  ActionSheetOptions,
  ActionSheetProps,
  ActionSheetRenderArgs,
  ActionSheetResult,
  ActionSheetSlots,
  ActionSheetVariant
} from '@skyroc/native-ui';

ActionSheetVariant

展示变体:default 为通栏文字列表,button 为带图标的卡片列表。

'default' | 'button'

ActionSheetSlots

操作列表可通过 classNames 覆盖的 slot 名称。

'action' | 'actionName' | 'actionSubname' | 'cancel' | 'cancelGap' | 'cancelName' | 'indicator' | 'root'

SheetSlots

内部 Sheet 可通过 sheetClassNames 覆盖的 slot 名称。

'background' | 'chrome' | 'close' | 'closeIcon' | 'description' | 'handle' | 'handleBar' | 'header' | 'title'

SlotClassNames

classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 ActionSheetSlots / SheetSlots。

Partial<Record<Slots, string>>

ActionSheetAction

单个操作项。

字段类型说明
value*string操作项的值,用于 value / onChange 匹配,同时作为列表 key。
name*ReactNode操作项名称,string / number 自动包裹 Text。
subnameReactNode操作项描述信息,显示在名称下方。
iconReactNode操作项图标,仅 variant="button" 时渲染。
colorstring文字颜色,直接写进 style,优先级高于选中态的主题色。
disabledboolean是否禁用,禁用后不可点击并降低透明度。
loadingboolean是否加载中,加载时名称与描述被指示器替换且不可点击。
classNamestring操作项根节点的类名,优先级高于 classNames.action。
callback() => void点击后的回调,与 onSelect 同时触发,用于把行为写在数据里。

ActionSheetRenderArgs

Render prop children 接收的参数。

字段类型说明
value*string当前选中值。
actionActionSheetAction当前选中的操作项,未选中时为 undefined。
toggle*() => void切换面板显示 / 隐藏。

ActionSheetResult

选中结果,由 showActionSheet 的 Promise 与 callback 一同结算。

字段类型说明
action*ActionSheetAction选中的操作项。
index*number选中项在 actions 中的索引。

ActionSheetOptions

showActionSheet 的入参,等价于 Omit<ActionSheetProps, 'children' | 'defaultShow' | 'onClosed' | 'onUpdateShow' | 'show' | 'value'> 再加上 callback。

字段类型说明
callback(result: ActionSheetResult | null) => void选中或取消后的通用回调,与 Promise 一同结算,且恰好触发一次。