ActionSheet
从底部弹出的操作面板,展示一组与当前上下文相关的操作
操作面板(ActionSheet)从屏幕底部升起,列出若干与当前上下文相关的操作。组件基于 Sheet 封装(底层是 @gorhom/bottom-sheet 的 BottomSheetModal),负责操作列表、选中态与取消按钮,面板本体的标题栏、遮罩、拖拽条仍由 Sheet 提供。
除了声明式的 <ActionSheet />,还提供命令式的 showActionSheet(),返回一个在选中或取消时结算的 Promise。
import { ActionSheet, closeActionSheet, showActionSheet } from '@skyroc/native-ui';面板挂在 BottomSheetModal 上,应用根节点需要包一层 GestureHandlerRootView 与 BottomSheetModalProvider;命令式调用还会把渲染器挂到 PortalHost 上,两者缺一面板都不会出现。
基础用法
actions 定义操作项,每项必须有唯一的 value(它同时是列表 key,因为 name 允许是任意节点)。show + onUpdateShow 控制显示,cancelText 有值时才渲染底部取消按钮。
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(描述)、disabled、loading 和 color:
| 字段 | 表现 |
|---|---|
subname | 在名称下方追加一行弱化描述 |
disabled | 降到 50% 不透明度并阻断点击 |
loading | 降到 70% 不透明度、阻断点击,并用指示器替换掉名称与描述 |
color | 直接写进 style.color,优先级高于选中态的主题色 |
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,取消按钮也变成同样的卡片。
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 };两个变体的差异:
| 维度 | default | button |
|---|---|---|
| 操作项外观 | 通栏文字行,行间用 Divider 分隔 | 圆角卡片,靠容器 gap-3 分隔 |
icon | 不渲染 | 渲染在名称之前 |
| 选中态 | 名称变主题色 | 名称变主题色 + 卡片描边与浅色底 |
| 取消按钮 | 与列表之间有一条灰色间隔(cancelGap) | 圆角卡片,无间隔条 |
button 变体的操作项基态就带了一圈 border-transparent,选中时只换颜色,行高不会跳动。
受控选择
value / onChange 控制当前选中值(非受控时用 defaultValue)。children 是一个 render prop,接收 { action, value, toggle },用于就地展示当前选中项并控制面板开合 —— 不用自己再维护一份显示文本,action.name 就是。
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 |
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 };把前三项全部关掉时,务必保留 cancelText 或 closeOnClickAction,否则面板将没有任何退出路径。
样式覆盖
className 与 classNames 作用于操作列表这一层,内部 Sheet 面板本体则由 sheetClassName / sheetClassNames 覆盖 —— 两组属性各管一层,不要混用。
| slot | 作用位置 |
|---|---|
root | 操作列表容器(含底部安全区内边距) |
action | 单个操作项的 Pressable |
actionName | 操作项名称文字 |
actionSubname | 操作项描述文字 |
indicator | loading 指示器的 colorClassName,只接受 accent-* 类 |
cancelGap | 取消按钮上方的灰色间隔条(仅 default 变体) |
cancel | 取消按钮的 Pressable |
cancelName | 取消按钮文字 |
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.className;className 排在 classNames.root 之后,同样是后者优先级更高。
命令式调用
showActionSheet(options) 直接弹出面板并返回 Promise<ActionSheetResult | null>:选中时 resolve { action, index },取消、点遮罩、下拉关闭时 resolve null。closeActionSheet() 从外部关闭当前面板,等待中的 Promise 会按取消结算而不是永远挂起。
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 | 非受控模式默认是否显示 | boolean | false |
| value | 受控选中值 | string | - |
| defaultValue | 非受控模式默认选中值 | string | '' |
| closeOnClickAction | 点击选项后是否自动关闭;命令式调用时默认为 true | boolean | false |
| closeable | 是否显示标题栏的关闭按钮 | boolean | true |
| closeOnBackdropPress | 是否允许点击遮罩关闭 | boolean | true |
| enablePanDownToClose | 是否允许下拉关闭 | boolean | true |
| showHandle | 是否显示顶部拖拽指示条 | boolean | true |
| children | Render 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 为带图标的卡片列表。
ActionSheetSlots
操作列表可通过 classNames 覆盖的 slot 名称。
SheetSlots
内部 Sheet 可通过 sheetClassNames 覆盖的 slot 名称。
SlotClassNames
classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 ActionSheetSlots / SheetSlots。
ActionSheetAction
单个操作项。
| 字段 | 类型 | 说明 |
|---|---|---|
| value* | string | 操作项的值,用于 value / onChange 匹配,同时作为列表 key。 |
| name* | ReactNode | 操作项名称,string / number 自动包裹 Text。 |
| subname | ReactNode | 操作项描述信息,显示在名称下方。 |
| icon | ReactNode | 操作项图标,仅 variant="button" 时渲染。 |
| color | string | 文字颜色,直接写进 style,优先级高于选中态的主题色。 |
| disabled | boolean | 是否禁用,禁用后不可点击并降低透明度。 |
| loading | boolean | 是否加载中,加载时名称与描述被指示器替换且不可点击。 |
| className | string | 操作项根节点的类名,优先级高于 classNames.action。 |
| callback | () => void | 点击后的回调,与 onSelect 同时触发,用于把行为写在数据里。 |
ActionSheetRenderArgs
Render prop children 接收的参数。
| 字段 | 类型 | 说明 |
|---|---|---|
| value* | string | 当前选中值。 |
| action | ActionSheetAction | 当前选中的操作项,未选中时为 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 一同结算,且恰好触发一次。 |