Skyroc Native UI

ShareSheet

分享面板,图标网格 + 取消按钮

分享面板(ShareSheet)在底部弹出一组图标选项,典型场景是分享到各平台。它基于 Sheet 封装,除组件式用法外还提供 showShareSheet 命令式调用(返回 Promise)。

import { ShareSheet, showShareSheet, closeShareSheet } from '@skyroc/native-ui';

依赖 @gorhom/bottom-sheet,请确保 App 根节点已经包了 GestureHandlerRootViewBottomSheetModalProvider;命令式调用还需要挂 PortalHost

基础用法

options 传一维数组即为单行。每个选项必须有唯一的 valuename 可能是节点,不能兼作 key)。选项超出宽度时可以横向滚动。

ShareSheetBasic.tsx
Loading…
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 通知)。

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

ShareSheetMultiRow.tsx
Loading…
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 显示在名称下方。

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

事件

onSelectonCancel 记录用户动作,onUpdateShow 是显隐变化,onClosed 在退场动画播完、面板真正卸载后触发。

ShareSheetEvents.tsx
Loading…
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(右上角关闭按钮)、closeOnBackdropPressenablePanDownToCloseshowHandle 可分别关闭。ref 原样透传给底层的 BottomSheetModal

ShareSheetCloseBehavior.tsx
Loading…
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 定制外层面板;namedescription 都支持节点。

slot作用位置
root交给 Sheet 的内容容器(底部安全区留白在这里)
row单行容器
options选项横向滚动区的 contentContainer
option单个选项容器
optionIcon选项图标的圆形底衬
optionName选项名称
optionDescription选项描述
cancelGap取消按钮上方的灰色间隔条
cancel取消按钮容器
cancelName取消按钮文字
ShareSheetStyles.tsx
Loading…
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 nullcallback 与 Promise 一同结算,且恰好触发一次。closeShareSheet() 从外部关闭,按取消结算 —— 等待中的 Promise 会 resolve 成 null 而不是永远挂起。

ShareSheetImperative.tsx
Loading…
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非受控初始显示状态booleanfalse
onUpdateShow显示状态变化回调(show: boolean) => void-
onSelect选项点击回调;index 是所在行内的下标,rowIndex 是行下标(option: ShareSheetOption, index: number, rowIndex: number) => void-
onCancel取消按钮点击回调() => void-
onClosed退场动画播完、面板真正卸载后触发() => void-
closeOnSelect点击选项后是否自动关闭booleanfalse
title面板标题ReactNode-
description描述信息,显示在标题下方ReactNode-
cancelText取消按钮文字,不设置则不显示取消按钮string-
closeable是否显示右上角关闭按钮booleantrue
closeOnBackdropPress是否允许点击遮罩关闭booleantrue
enablePanDownToClose是否允许下拉关闭booleantrue
showHandle是否显示顶部拖拽指示条booleantrue
className内容容器类名,合并在 classNames.root 之后string-
classNames各 slot 的类名覆盖,见「自定义内容与样式」一节SlotClassNames<ShareSheetSlots>-
sheetClassName内部 Sheet 面板本体(背景 + 圆角)的类名string-
sheetClassNames内部 Sheet 各 slot 的类名覆盖SlotClassNames<SheetSlots>-
ref底层 BottomSheetModal 的实例引用,用于 snapToIndex / expand / collapseRef<BottomSheetModal>-

showShareSheet

function showShareSheet(options: ShareSheetCallOptions): Promise<ShareSheetResult | null>;
function closeShareSheet(): void;

ShareSheetCallOptionsShareSheetProps 去掉 show / defaultShow / onUpdateShow / onClosed,另加一个 callback

属性说明类型默认值
callback选中或取消后的通用回调,与 Promise 一同结算,且恰好触发一次(result: ShareSheetResult | null) => void-
closeOnSelect点击选项后是否自动关闭;命令式调用下默认为 truebooleantrue

类型

import type {
  ShareSheetCallOptions,
  ShareSheetOption,
  ShareSheetOptions,
  ShareSheetProps,
  ShareSheetResult,
  ShareSheetSlots
} from '@skyroc/native-ui';

ShareSheetOptions

选项列表:一维数组为单行,二维数组为多行,行与行之间用分割线隔开。

ShareSheetSlots

可通过 classNames 覆盖的 slot 名称。

'cancel' | 'cancelGap' | 'cancelName' | 'option' | 'optionDescription' | 'optionIcon' | 'optionName' | 'options' | 'root' | 'row'

SlotClassNames

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

Partial<Record<Slots, string>>

ShareSheetOption

单个分享选项。

字段类型说明
value*string选项值,同时作为列表 key(必传,因为 name 可能是节点)。
name*ReactNode选项名称。
iconReactNode图标节点。
descriptionReactNode选项描述。
classNamestring该选项根节点的类名;改字色请用 classNames.optionName。

ShareSheetResult

showShareSheet 的结算结果。

字段类型说明
option*ShareSheetOption选中的选项。
index*number选中项在所在行内的下标。
rowIndex*number选中项所在行的下标,单行写法恒为 0。

包内还导出了 shareSheetVariantsShareSheetRenderer 与状态管理器 shareSheetManager(及其类型 ShareSheetEntry / ShareSheetEntryOptions / ShareSheetSettle),一般不需要直接使用。