ShareSheet
分享面板,图标网格 + 取消按钮
分享面板(ShareSheet)在底部弹出一组图标选项,典型场景是分享到各平台。它基于 Sheet 封装,除组件式用法外还提供 showShareSheet 命令式调用(返回 Promise)。
import { ShareSheet, showShareSheet, closeShareSheet } from '@skyroc/native-ui';依赖 @gorhom/bottom-sheet,请确保 App 根节点已经包了 GestureHandlerRootView 与 BottomSheetModalProvider;命令式调用还需要挂 PortalHost。
基础用法
options 传一维数组即为单行。每个选项必须有唯一的 value(name 可能是节点,不能兼作 key)。选项超出宽度时可以横向滚动。
import AntDesign from '@expo/vector-icons/AntDesign';
import type { ShareSheetOption } from '@skyroc/native-ui';
import { Button, ShareSheet } from '@skyroc/native-ui';
import type { ComponentProps } from 'react';
import { useState } from 'react';
import { View } from 'react-native';
/** 选项图标统一走 AntDesign,尺寸与 optionIcon 槽的 size-12 对齐 */
const ICON_SIZE = 22;
function renderIcon(name: ComponentProps<typeof AntDesign>['name']) {
return (
<AntDesign
color="var(--foreground)"
name={name}
size={ICON_SIZE}
/>
);
}
/** 单行写法,onSelect 的 rowIndex 恒为 0 */
const OPTIONS: ShareSheetOption[] = [
{ icon: renderIcon('wechat'), name: '微信', value: 'wechat' },
{ icon: renderIcon('weibo'), name: '微博', value: 'weibo' },
{ icon: renderIcon('qq'), name: 'QQ', value: 'qq' },
{ icon: renderIcon('link'), name: '复制链接', value: 'link' }
];
const ShareSheetBasic = () => {
const [show, setShow] = useState(false);
return (
<View className="flex-row flex-wrap items-center gap-3 bg-background p-4">
<Button
variant="tonal"
onPress={() => setShow(true)}
>
立即分享
</Button>
<ShareSheet
closeOnSelect
cancelText="取消"
options={OPTIONS}
show={show}
title="立即分享给好友"
onUpdateShow={setShow}
/>
</View>
);
};
export { ShareSheetBasic };何时使用
- 分享到第三方平台,或一组以图标为主的并列操作。
- 纯文字的动作列表用
ActionSheet;自定义内容的底部面板用Sheet。
非受控显示
defaultShow 设置初始显示状态,后续显隐由组件内部维护(仍会通过 onUpdateShow 通知)。
import AntDesign from '@expo/vector-icons/AntDesign';
import type { ShareSheetOption } from '@skyroc/native-ui';
import { Button, ShareSheet } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const OPTIONS: ShareSheetOption[] = [
{
icon: (
<AntDesign
color="var(--foreground)"
name="link"
size={22}
/>
),
name: '复制链接',
value: 'link'
}
];
const ShareSheetUncontrolled = () => {
const [mounted, setMounted] = useState(false);
return (
<View className="bg-background p-4">
<Button
variant="tonal"
onPress={() => setMounted(true)}
>
按默认状态打开
</Button>
{mounted ? (
<ShareSheet
closeOnSelect
defaultShow
cancelText="取消"
options={OPTIONS}
title="非受控分享面板"
onClosed={() => setMounted(false)}
/>
) : null}
</View>
);
};
export { ShareSheetUncontrolled };多行展示
options 传二维数组即按行渲染,行与行之间用分割线隔开。onSelect 的第二个参数是选项在所在行内的下标,第三个参数是行下标;单行写法等价于只有一行,rowIndex 恒为 0。要跨行唯一标识一项,直接用 option.value。
import AntDesign from '@expo/vector-icons/AntDesign';
import type { ShareSheetOption } from '@skyroc/native-ui';
import { Button, ShareSheet } from '@skyroc/native-ui';
import type { ComponentProps } from 'react';
import { useState } from 'react';
import { View } from 'react-native';
/** 选项图标统一走 AntDesign,尺寸与 optionIcon 槽的 size-12 对齐 */
const ICON_SIZE = 22;
function renderIcon(name: ComponentProps<typeof AntDesign>['name']) {
return (
<AntDesign
color="var(--foreground)"
name={name}
size={ICON_SIZE}
/>
);
}
/** 二维数组,行与行之间自动画分割线 */
const OPTIONS: ShareSheetOption[][] = [
[
{ icon: renderIcon('wechat'), name: '微信', value: 'wechat' },
{ icon: renderIcon('weibo'), name: '微博', value: 'weibo' },
{ icon: renderIcon('qq'), name: 'QQ', value: 'qq' },
{ icon: renderIcon('mail'), name: '邮件', value: 'mail' }
],
[
{ icon: renderIcon('link'), name: '复制链接', value: 'link' },
{ icon: renderIcon('qrcode'), name: '二维码', value: 'qrcode' },
{ icon: renderIcon('star'), name: '收藏', value: 'star' },
{ icon: renderIcon('printer'), name: '打印', value: 'print' }
]
];
const ShareSheetMultiRow = () => {
const [show, setShow] = useState(false);
return (
<View className="flex-row flex-wrap items-center gap-3 bg-background p-4">
<Button
variant="tonal"
onPress={() => setShow(true)}
>
打开面板
</Button>
<ShareSheet
closeOnSelect
cancelText="取消"
options={OPTIONS}
show={show}
title="分享到"
onUpdateShow={setShow}
/>
</View>
);
};
export { ShareSheetMultiRow };描述信息
面板整体的 description 显示在标题下方,单个选项的 description 显示在名称下方。
import AntDesign from '@expo/vector-icons/AntDesign';
import type { ShareSheetOption } from '@skyroc/native-ui';
import { Button, ShareSheet } from '@skyroc/native-ui';
import type { ComponentProps } from 'react';
import { useState } from 'react';
import { View } from 'react-native';
/** 选项图标统一走 AntDesign,尺寸与 optionIcon 槽的 size-12 对齐 */
const ICON_SIZE = 22;
function renderIcon(name: ComponentProps<typeof AntDesign>['name']) {
return (
<AntDesign
color="var(--foreground)"
name={name}
size={ICON_SIZE}
/>
);
}
/** 选项名称下方再挂一行说明 */
const OPTIONS: ShareSheetOption[] = [
{ description: '分享给好友', icon: renderIcon('wechat'), name: '微信', value: 'wechat' },
{ description: '公开可见', icon: renderIcon('weibo'), name: '微博', value: 'weibo' },
{ description: '有效期 7 天', icon: renderIcon('link'), name: '复制链接', value: 'link' }
];
const ShareSheetDescription = () => {
const [show, setShow] = useState(false);
return (
<View className="flex-row flex-wrap items-center gap-3 bg-background p-4">
<Button
variant="tonal"
onPress={() => setShow(true)}
>
打开面板
</Button>
<ShareSheet
closeOnSelect
cancelText="取消"
description="分享后对方可以直接查看"
options={OPTIONS}
show={show}
title="分享单张海报"
onUpdateShow={setShow}
/>
</View>
);
};
export { ShareSheetDescription };事件
onSelect 与 onCancel 记录用户动作,onUpdateShow 是显隐变化,onClosed 在退场动画播完、面板真正卸载后触发。
import AntDesign from '@expo/vector-icons/AntDesign';
import type { ShareSheetOption } from '@skyroc/native-ui';
import { Button, ShareSheet, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const OPTIONS: ShareSheetOption[] = [
{
icon: (
<AntDesign
color="var(--foreground)"
name="link"
size={22}
/>
),
name: '复制链接',
value: 'link'
},
{
icon: (
<AntDesign
color="var(--foreground)"
name="qrcode"
size={22}
/>
),
name: '二维码',
value: 'qrcode'
}
];
const ShareSheetEvents = () => {
const [show, setShow] = useState(false);
const [event, setEvent] = useState('等待操作');
return (
<View className="gap-3 bg-background p-4">
<Text className="text-sm text-muted-foreground">最近事件:{event}</Text>
<Button
variant="tonal"
onPress={() => setShow(true)}
>
打开事件示例
</Button>
<ShareSheet
closeOnSelect
cancelText="取消"
options={OPTIONS}
show={show}
title="选择一项"
onCancel={() => setEvent('onCancel')}
onClosed={() => setEvent(previous => `${previous} → onClosed`)}
onSelect={option => setEvent(`onSelect: ${option.value}`)}
onUpdateShow={setShow}
/>
</View>
);
};
export { ShareSheetEvents };onUpdateShow(false) 是「请求关闭」,此时动画才刚开始;命令式场景要等 onClosed 才能安全移除节点,否则面板会硬闪消失。
closeOnSelect 默认 false(组件式用法里通常由外部状态决定何时关),命令式调用则默认打开 —— 那边没有外部状态承接,不自动关面板会一直挂着。
关闭行为与 ref
closeable(右上角关闭按钮)、closeOnBackdropPress、enablePanDownToClose、showHandle 可分别关闭。ref 原样透传给底层的 BottomSheetModal。
import AntDesign from '@expo/vector-icons/AntDesign';
import type { ShareSheetOption } from '@skyroc/native-ui';
import { BottomSheetModal, Button, ShareSheet } from '@skyroc/native-ui';
import type { ComponentRef } from 'react';
import { useRef, useState } from 'react';
import { View } from 'react-native';
const OPTIONS: ShareSheetOption[] = [
{
icon: (
<AntDesign
color="var(--foreground)"
name="check-circle"
size={22}
/>
),
name: '确认',
value: 'confirm'
}
];
const ShareSheetCloseBehavior = () => {
const [show, setShow] = useState(false);
const sheetRef = useRef<ComponentRef<typeof BottomSheetModal>>(null);
function handleOpenAndDismissByRef() {
setShow(true);
setTimeout(() => sheetRef.current?.dismiss(), 2500);
}
return (
<View className="bg-background p-4">
<Button
variant="tonal"
onPress={() => setShow(true)}
>
仅保留取消入口
</Button>
<Button
className="mt-3"
variant="outline"
onPress={handleOpenAndDismissByRef}
>
打开后用 ref 关闭
</Button>
<ShareSheet
ref={sheetRef}
cancelText="关闭面板"
closeable={false}
closeOnBackdropPress={false}
enablePanDownToClose={false}
options={OPTIONS}
show={show}
showHandle={false}
title="关闭行为"
onUpdateShow={setShow}
/>
</View>
);
};
export { ShareSheetCloseBehavior };自定义内容与样式
className / classNames 定制内容与选项,sheetClassName / sheetClassNames 定制外层面板;name 与 description 都支持节点。
| slot | 作用位置 |
|---|---|
root | 交给 Sheet 的内容容器(底部安全区留白在这里) |
row | 单行容器 |
options | 选项横向滚动区的 contentContainer |
option | 单个选项容器 |
optionIcon | 选项图标的圆形底衬 |
optionName | 选项名称 |
optionDescription | 选项描述 |
cancelGap | 取消按钮上方的灰色间隔条 |
cancel | 取消按钮容器 |
cancelName | 取消按钮文字 |
import AntDesign from '@expo/vector-icons/AntDesign';
import type { ShareSheetOption } from '@skyroc/native-ui';
import { Button, ShareSheet, Text } from '@skyroc/native-ui';
import type { ComponentProps } from 'react';
import { useState } from 'react';
import { View } from 'react-native';
/** 选项图标统一走 AntDesign,尺寸与 optionIcon 槽的 size-12 对齐 */
const ICON_SIZE = 22;
function renderIcon(name: ComponentProps<typeof AntDesign>['name']) {
return (
<AntDesign
color="var(--foreground)"
name={name}
size={ICON_SIZE}
/>
);
}
const OPTIONS: ShareSheetOption[] = [
{
className: 'rounded-xl bg-primary/5 py-2',
description: <Text className="text-xs text-success">常用</Text>,
icon: renderIcon('wechat'),
name: <Text className="mt-2 text-xs font-semibold text-primary">微信</Text>,
value: 'wechat'
},
{ icon: renderIcon('weibo'), name: '微博', value: 'weibo' },
{ icon: renderIcon('qq'), name: 'QQ', value: 'qq' },
{ icon: renderIcon('link'), name: '复制链接', value: 'link' }
];
const ShareSheetStyles = () => {
const [show, setShow] = useState(false);
return (
<View className="flex-row flex-wrap items-center gap-3 bg-background p-4">
<Button
variant="tonal"
onPress={() => setShow(true)}
>
打开面板
</Button>
<ShareSheet
closeOnSelect
cancelText="再想想"
className="bg-primary/5"
classNames={{
cancelName: 'text-primary',
optionIcon: 'bg-primary/10',
optionName: 'font-medium text-primary'
}}
options={OPTIONS}
sheetClassName="border border-primary/20"
sheetClassNames={{ title: 'text-primary' }}
show={show}
title="自定义样式"
onUpdateShow={setShow}
/>
</View>
);
};
export { ShareSheetStyles };options 的类名落在横向 ScrollView 的 contentContainer 上而不是 ScrollView 本身 —— 左右留白写在容器上会被算进滚动视口,首尾两项就贴边了。单个选项还可以用 ShareSheetOption.className 单独覆盖;注意 RN 的文字颜色不从父节点继承,改字色请用 classNames.optionName。
命令式调用
showShareSheet(options) 返回 Promise:选中 resolve 结果对象,取消 / 遮罩关闭 resolve null;callback 与 Promise 一同结算,且恰好触发一次。closeShareSheet() 从外部关闭,按取消结算 —— 等待中的 Promise 会 resolve 成 null 而不是永远挂起。
import AntDesign from '@expo/vector-icons/AntDesign';
import type { ShareSheetOption } from '@skyroc/native-ui';
import { Button, Text, closeShareSheet, showShareSheet } from '@skyroc/native-ui';
import type { ComponentProps } from 'react';
import { useState } from 'react';
import { View } from 'react-native';
/** 选项图标统一走 AntDesign,尺寸与 optionIcon 槽的 size-12 对齐 */
const ICON_SIZE = 22;
function renderIcon(name: ComponentProps<typeof AntDesign>['name']) {
return (
<AntDesign
color="var(--foreground)"
name={name}
size={ICON_SIZE}
/>
);
}
const SINGLE_ROW_OPTIONS: ShareSheetOption[] = [
{ icon: renderIcon('wechat'), name: '微信', value: 'wechat' },
{ icon: renderIcon('weibo'), name: '微博', value: 'weibo' },
{ icon: renderIcon('qq'), name: 'QQ', value: 'qq' },
{ icon: renderIcon('link'), name: '复制链接', value: 'link' }
];
const MULTI_ROW_OPTIONS: ShareSheetOption[][] = [
[
{ icon: renderIcon('wechat'), name: '微信', value: 'wechat' },
{ icon: renderIcon('weibo'), name: '微博', value: 'weibo' },
{ icon: renderIcon('qq'), name: 'QQ', value: 'qq' },
{ icon: renderIcon('mail'), name: '邮件', value: 'mail' }
],
[
{ icon: renderIcon('link'), name: '复制链接', value: 'link' },
{ icon: renderIcon('qrcode'), name: '二维码', value: 'qrcode' },
{ icon: renderIcon('star'), name: '收藏', value: 'star' },
{ icon: renderIcon('printer'), name: '打印', value: 'print' }
]
];
const ShareSheetImperative = () => {
const [lastResult, setLastResult] = useState('—');
const [lastCallback, setLastCallback] = useState('—');
async function handleImperative() {
const result = await showShareSheet({
cancelText: '取消',
callback: value => setLastCallback(value ? value.option.value : 'null'),
description: '选中或取消后 Promise 都会结算,取消时得到 null',
options: MULTI_ROW_OPTIONS,
title: '命令式调用'
});
setLastResult(result ? `选中 ${result.option.value}(第 ${result.rowIndex} 行第 ${result.index} 项)` : '已取消');
}
function handleAutoClose() {
showShareSheet({ options: SINGLE_ROW_OPTIONS, title: '两秒后自动关闭' });
// 外部关闭同样按取消结算,上面那个 Promise 不会挂死
setTimeout(closeShareSheet, 2000);
}
return (
<View className="gap-3 bg-background p-4">
<Text color="muted">上次结果:{lastResult}</Text>
<Text color="muted">callback:{lastCallback}</Text>
<View className="flex-row flex-wrap items-center gap-3">
<Button
variant="tonal"
onPress={handleImperative}
>
showShareSheet
</Button>
<Button
variant="outline"
onPress={handleAutoClose}
>
两秒后自动关闭
</Button>
</View>
</View>
);
};
export { ShareSheetImperative };const result = await showShareSheet({
options: [
{ icon: <WeChatIcon />, name: '微信', value: 'wechat' },
{ icon: <WeiboIcon />, name: '微博', value: 'weibo' }
],
title: '分享到'
});
if (result) {
console.log(result.option.value, result.index, result.rowIndex);
}同一时刻只显示一个命令式面板(单例);show / defaultShow / onUpdateShow / onClosed 在这条路径上不开放,显隐由内部的 Renderer 托管。
API
ShareSheet
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| options | 分享选项列表,一维为单行、二维为多行 | ShareSheetOptions | [] |
| show | 是否显示面板(受控) | boolean | - |
| defaultShow | 非受控初始显示状态 | boolean | false |
| onUpdateShow | 显示状态变化回调 | (show: boolean) => void | - |
| onSelect | 选项点击回调;index 是所在行内的下标,rowIndex 是行下标 | (option: ShareSheetOption, index: number, rowIndex: number) => void | - |
| onCancel | 取消按钮点击回调 | () => void | - |
| onClosed | 退场动画播完、面板真正卸载后触发 | () => void | - |
| closeOnSelect | 点击选项后是否自动关闭 | boolean | false |
| title | 面板标题 | ReactNode | - |
| description | 描述信息,显示在标题下方 | ReactNode | - |
| cancelText | 取消按钮文字,不设置则不显示取消按钮 | string | - |
| closeable | 是否显示右上角关闭按钮 | boolean | true |
| closeOnBackdropPress | 是否允许点击遮罩关闭 | boolean | true |
| enablePanDownToClose | 是否允许下拉关闭 | boolean | true |
| showHandle | 是否显示顶部拖拽指示条 | boolean | true |
| className | 内容容器类名,合并在 classNames.root 之后 | string | - |
| classNames | 各 slot 的类名覆盖,见「自定义内容与样式」一节 | SlotClassNames<ShareSheetSlots> | - |
| sheetClassName | 内部 Sheet 面板本体(背景 + 圆角)的类名 | string | - |
| sheetClassNames | 内部 Sheet 各 slot 的类名覆盖 | SlotClassNames<SheetSlots> | - |
| ref | 底层 BottomSheetModal 的实例引用,用于 snapToIndex / expand / collapse | Ref<BottomSheetModal> | - |
showShareSheet
function showShareSheet(options: ShareSheetCallOptions): Promise<ShareSheetResult | null>;
function closeShareSheet(): void;ShareSheetCallOptions 即 ShareSheetProps 去掉 show / defaultShow / onUpdateShow / onClosed,另加一个 callback:
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| callback | 选中或取消后的通用回调,与 Promise 一同结算,且恰好触发一次 | (result: ShareSheetResult | null) => void | - |
| closeOnSelect | 点击选项后是否自动关闭;命令式调用下默认为 true | boolean | true |
类型
import type {
ShareSheetCallOptions,
ShareSheetOption,
ShareSheetOptions,
ShareSheetProps,
ShareSheetResult,
ShareSheetSlots
} from '@skyroc/native-ui';ShareSheetSlots
可通过 classNames 覆盖的 slot 名称。
SlotClassNames
classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 ShareSheetSlots。
ShareSheetOption
单个分享选项。
| 字段 | 类型 | 说明 |
|---|---|---|
| value* | string | 选项值,同时作为列表 key(必传,因为 name 可能是节点)。 |
| name* | ReactNode | 选项名称。 |
| icon | ReactNode | 图标节点。 |
| description | ReactNode | 选项描述。 |
| className | string | 该选项根节点的类名;改字色请用 classNames.optionName。 |
ShareSheetResult
showShareSheet 的结算结果。
| 字段 | 类型 | 说明 |
|---|---|---|
| option* | ShareSheetOption | 选中的选项。 |
| index* | number | 选中项在所在行内的下标。 |
| rowIndex* | number | 选中项所在行的下标,单行写法恒为 0。 |
包内还导出了 shareSheetVariants、ShareSheetRenderer 与状态管理器 shareSheetManager(及其类型 ShareSheetEntry / ShareSheetEntryOptions / ShareSheetSettle),一般不需要直接使用。