Skip to content
Lenso UI

Drawer 抽屉

用于补充内容与操作的滑出面板

用法

import { Drawer, Button } from "@lenso/ui";

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 drawer-basic (Apache-2.0).import { Button, Drawer } from "@lenso/ui";
export function Basic() {  return (    <Drawer.Provider>      <Drawer swipeDirection="right">        <Drawer.Trigger render={<Button variant="secondary" />}>Open Drawer</Drawer.Trigger>        <Drawer.Portal>          <Drawer.Backdrop />          <Drawer.Viewport>            <Drawer.Popup>              <Drawer.Content>                <Drawer.Header>                  <Drawer.Title>Drawer Title</Drawer.Title>                </Drawer.Header>                <Drawer.Body>                  <p>                    This is a bottom drawer built with React Aria's Modal component. It slides up                    from the bottom of the screen with a smooth CSS transition.                  </p>                </Drawer.Body>                <Drawer.Footer>                  <Drawer.Close render={<Button variant="secondary" />}>Cancel</Drawer.Close>                  <Drawer.Close render={<Button />}>Confirm</Drawer.Close>                </Drawer.Footer>              </Drawer.Content>            </Drawer.Popup>          </Drawer.Viewport>        </Drawer.Portal>      </Drawer>    </Drawer.Provider>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

组件结构

import { Drawer, Button } from "@lenso/ui";
export default () => (  <Drawer>    <Button>Open Drawer</Button>    <Drawer.Backdrop>      <Drawer.Content>        <Drawer.Dialog>          <Drawer.Handle /> {/* Optional: Drag handle */}          <Drawer.CloseTrigger /> {/* Optional: Close button */}          <Drawer.Header>            <Drawer.Heading />          </Drawer.Header>          <Drawer.Body />          <Drawer.Footer />        </Drawer.Dialog>      </Drawer.Content>    </Drawer.Backdrop>  </Drawer>);

示例

弹出位置

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 drawer-placements (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { Button, CloseIcon, Drawer } from "@lenso/ui";import { styles } from "./styles";
export function Placements() {  const placements = ["bottom", "top", "left", "right"] as const;  return (    <div {...stylex.props(styles.row)}>      {placements.map((placement) => (        <Drawer.Provider key={placement}>          <Drawer            swipeDirection={              placement === "bottom" ? "down" : placement === "top" ? "up" : placement            }          >            <Drawer.Trigger render={<Button variant="secondary" />}>              {placement.charAt(0).toUpperCase() + placement.slice(1)}            </Drawer.Trigger>            <Drawer.Portal>              <Drawer.Backdrop />              <Drawer.Viewport>                <Drawer.Popup>                  <Drawer.Content>                    <Drawer.Close                      aria-label="Close drawer"                      render={                        <Button                          isIconOnly                          size="sm"                          variant="tertiary"                          xstyle={styles.cornerClose}                        />                      }                    >                      <CloseIcon />                    </Drawer.Close>                    {placement === "bottom" && <Drawer.Handle />}                    <Drawer.Header>                      <Drawer.Title>                        {placement.charAt(0).toUpperCase() + placement.slice(1)} Drawer                      </Drawer.Title>                    </Drawer.Header>                    <Drawer.Body>                      <p>                        This drawer slides in from the <strong>{placement}</strong> edge of the                        screen.                      </p>                    </Drawer.Body>                    <Drawer.Footer>                      <Drawer.Close render={<Button variant="secondary" />}>Cancel</Drawer.Close>                      <Drawer.Close render={<Button />}>Done</Drawer.Close>                    </Drawer.Footer>                    {placement === "top" && <Drawer.Handle />}                  </Drawer.Content>                </Drawer.Popup>              </Drawer.Viewport>            </Drawer.Portal>          </Drawer>        </Drawer.Provider>      ))}    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

不可关闭

在 Drawer.Backdrop 上设置 isDismissable={false} 可阻止点击外部或拖拽关闭。用户必须通过抽屉内的操作按钮交互。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 drawer-non-dismissable (Apache-2.0).import { Button, Drawer } from "@lenso/ui";
export function NonDismissable() {  return (    <Drawer.Provider>      <Drawer        swipeDirection="down"        onOpenChange={(_open, details) => {          if (details.reason === "outside-press" || details.reason === "swipe") details.cancel();        }}      >        <Drawer.Trigger render={<Button variant="secondary" />}>Important Action</Drawer.Trigger>        <Drawer.Portal>          <Drawer.Backdrop />          <Drawer.Viewport>            <Drawer.Popup>              <Drawer.Content>                <Drawer.Header>                  <Drawer.Title>Confirm Action</Drawer.Title>                </Drawer.Header>                <Drawer.Body>                  <p>                    This drawer cannot be dismissed by clicking outside or dragging. You must use                    one of the buttons below.                  </p>                </Drawer.Body>                <Drawer.Footer>                  <Drawer.Close render={<Button variant="secondary" />}>Cancel</Drawer.Close>                  <Drawer.Close render={<Button />}>Confirm</Drawer.Close>                </Drawer.Footer>              </Drawer.Content>            </Drawer.Popup>          </Drawer.Viewport>        </Drawer.Portal>      </Drawer>    </Drawer.Provider>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

可滚动内容

Drawer.Body 通过原生滚动自动处理溢出。拖拽关闭在 body 区域被排除,以避免与滚动冲突。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 drawer-scrollable-content (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { Button, CloseIcon, Drawer } from "@lenso/ui";import { styles } from "./styles";
export function ScrollableContent() {  return (    <Drawer.Provider>      <Drawer swipeDirection="down">        <Drawer.Trigger render={<Button variant="secondary" />}>Terms & Conditions</Drawer.Trigger>        <Drawer.Portal>          <Drawer.Backdrop />          <Drawer.Viewport>            <Drawer.Popup>              <Drawer.Content>                <Drawer.Handle />                <Drawer.Close                  aria-label="Close drawer"                  render={                    <Button isIconOnly size="sm" variant="tertiary" xstyle={styles.cornerClose} />                  }                >                  <CloseIcon />                </Drawer.Close>                <Drawer.Header>                  <Drawer.Title>Terms & Conditions</Drawer.Title>                </Drawer.Header>                <Drawer.Body>                  {Array.from({ length: 20 }, (_, i) => (                    <p key={i} {...stylex.props(styles.paragraph)}>                      Paragraph {i + 1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit.                      Nullam pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet                      hendrerit risus, sed porttitor quam.                    </p>                  ))}                </Drawer.Body>                <Drawer.Footer>                  <Drawer.Close render={<Button variant="secondary" />}>Decline</Drawer.Close>                  <Drawer.Close render={<Button />}>Accept</Drawer.Close>                </Drawer.Footer>              </Drawer.Content>            </Drawer.Popup>          </Drawer.Viewport>        </Drawer.Portal>      </Drawer>    </Drawer.Provider>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

受控模式

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 drawer-controlled (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { Button, CloseIcon, Drawer, useOverlayState } from "@lenso/ui";import React from "react";import { styles } from "./styles";
export function Controlled() {  const [isOpen, setIsOpen] = React.useState(false);  const state = useOverlayState();  return (    <div {...stylex.props(styles.controlled)}>      <div {...stylex.props(styles.section)}>        <h3 {...stylex.props(styles.heading)}>With React.useState()</h3>        <p {...stylex.props(styles.explanation)}>          Control the drawer using React's <code>useState</code> hook for simple state management.        </p>        <Drawer.Provider>          <Drawer open={isOpen} onOpenChange={setIsOpen} swipeDirection="right">            <div {...stylex.props(styles.stateCard)}>              <p {...stylex.props(styles.status)}>                Status:{" "}                <span {...stylex.props(styles.statusValue)}>{isOpen ? "open" : "closed"}</span>              </p>              <div {...stylex.props(styles.actions)}>                <Drawer.Trigger render={<Button size="sm" variant="secondary" />}>                  Open Drawer                </Drawer.Trigger>                <Button size="sm" variant="tertiary" onClick={() => setIsOpen(!isOpen)}>                  Toggle                </Button>              </div>            </div>            <Drawer.Portal>              <Drawer.Backdrop />              <Drawer.Viewport>                <Drawer.Popup>                  <Drawer.Content>                    <Drawer.Close                      aria-label="Close drawer"                      render={                        <Button                          isIconOnly                          size="sm"                          variant="tertiary"                          xstyle={styles.cornerClose}                        />                      }                    >                      <CloseIcon />                    </Drawer.Close>                    <Drawer.Header>                      <Drawer.Title>Controlled with useState()</Drawer.Title>                    </Drawer.Header>                    <Drawer.Body>                      <p>                        This drawer is controlled by React's <code>useState</code> hook. Pass{" "}                        <code>isOpen</code> and <code>onOpenChange</code> props to manage the drawer                        state externally.                      </p>                    </Drawer.Body>                    <Drawer.Footer>                      <Drawer.Close render={<Button variant="secondary" />}>Close</Drawer.Close>                    </Drawer.Footer>                  </Drawer.Content>                </Drawer.Popup>              </Drawer.Viewport>            </Drawer.Portal>          </Drawer>        </Drawer.Provider>      </div>      <div {...stylex.props(styles.section)}>        <h3 {...stylex.props(styles.heading)}>With useOverlayState()</h3>        <p {...stylex.props(styles.explanation)}>          Use the <code>useOverlayState</code> hook for a cleaner API with convenient methods like{" "}          <code>open()</code>, <code>close()</code>, and <code>toggle()</code>.        </p>        <Drawer.Provider>          <Drawer open={state.isOpen} onOpenChange={state.setOpen} swipeDirection="right">            <div {...stylex.props(styles.stateCard)}>              <p {...stylex.props(styles.status)}>                Status:{" "}                <span {...stylex.props(styles.statusValue)}>                  {state.isOpen ? "open" : "closed"}                </span>              </p>              <div {...stylex.props(styles.actions)}>                <Drawer.Trigger                  render={<Button size="sm" variant="secondary" />}                  onClick={state.open}                >                  Open Drawer                </Drawer.Trigger>                <Button size="sm" variant="tertiary" onClick={state.toggle}>                  Toggle                </Button>              </div>            </div>            <Drawer.Portal>              <Drawer.Backdrop />              <Drawer.Viewport>                <Drawer.Popup>                  <Drawer.Content>                    <Drawer.Close                      aria-label="Close drawer"                      render={                        <Button                          isIconOnly                          size="sm"                          variant="tertiary"                          xstyle={styles.cornerClose}                        />                      }                    >                      <CloseIcon />                    </Drawer.Close>                    <Drawer.Header>                      <Drawer.Title>Controlled with useOverlayState()</Drawer.Title>                    </Drawer.Header>                    <Drawer.Body>                      <p>                        The <code>useOverlayState</code> hook provides dedicated methods for common                        operations. No need to manually create callbacks—just use{" "}                        <code>state.open()</code>, <code>state.close()</code>, or{" "}                        <code>state.toggle()</code>.                      </p>                    </Drawer.Body>                    <Drawer.Footer>                      <Drawer.Close render={<Button variant="secondary" />}>Close</Drawer.Close>                    </Drawer.Footer>                  </Drawer.Content>                </Drawer.Popup>              </Drawer.Viewport>            </Drawer.Portal>          </Drawer>        </Drawer.Provider>      </div>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

搭配表单

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 drawer-with-form (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { Button, CloseIcon, Drawer, Input, Label, TextField } from "@lenso/ui";import { styles } from "./styles";
export function WithForm() {  return (    <Drawer.Provider>      <Drawer swipeDirection="right">        <Drawer.Trigger render={<Button variant="secondary" />}>Edit Profile</Drawer.Trigger>        <Drawer.Portal>          <Drawer.Backdrop />          <Drawer.Viewport>            <Drawer.Popup>              <Drawer.Content>                <Drawer.Close                  aria-label="Close drawer"                  render={                    <Button isIconOnly size="sm" variant="tertiary" xstyle={styles.cornerClose} />                  }                >                  <CloseIcon />                </Drawer.Close>                <Drawer.Header>                  <Drawer.Title>Edit Profile</Drawer.Title>                </Drawer.Header>                <Drawer.Body>                  <form {...stylex.props(styles.form)}>                    <TextField fullWidth name="name">                      <Label>Name</Label>                      <Input type="text" placeholder="Enter your name" variant="secondary" />                    </TextField>                    <TextField fullWidth name="email">                      <Label>Email</Label>                      <Input type="email" placeholder="Enter your email" variant="secondary" />                    </TextField>                    <TextField fullWidth name="bio">                      <Label>Bio</Label>                      <Input placeholder="Tell us about yourself" variant="secondary" />                    </TextField>                  </form>                </Drawer.Body>                <Drawer.Footer>                  <Drawer.Close render={<Button variant="secondary" />}>Cancel</Drawer.Close>                  <Drawer.Close render={<Button />}>Save Changes</Drawer.Close>                </Drawer.Footer>              </Drawer.Content>            </Drawer.Popup>          </Drawer.Viewport>        </Drawer.Portal>      </Drawer>    </Drawer.Provider>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

导航抽屉

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 drawer-navigation (Apache-2.0).import type { ComponentType, SVGProps } from "react";import { Bars, Bell, Envelope, Gear, House, Magnifier, Person } from "@gravity-ui/icons";import * as stylex from "@stylexjs/stylex";import { Button, CloseIcon, Drawer } from "@lenso/ui";import { styles } from "./styles";
export function Navigation() {  const navItems: { icon: ComponentType<SVGProps<SVGSVGElement>>; label: string }[] = [    { icon: House, label: "Home" },    { icon: Magnifier, label: "Search" },    { icon: Bell, label: "Notifications" },    { icon: Envelope, label: "Messages" },    { icon: Person, label: "Profile" },    { icon: Gear, label: "Settings" },  ];  return (    <Drawer.Provider>      <Drawer swipeDirection="left">        <Drawer.Trigger render={<Button variant="secondary" />}>          <Button.Icon>            <Bars />          </Button.Icon>          Menu        </Drawer.Trigger>        <Drawer.Portal>          <Drawer.Backdrop />          <Drawer.Viewport>            <Drawer.Popup>              <Drawer.Content>                <Drawer.Close                  aria-label="Close drawer"                  render={                    <Button isIconOnly size="sm" variant="tertiary" xstyle={styles.cornerClose} />                  }                >                  <CloseIcon />                </Drawer.Close>                <Drawer.Header>                  <Drawer.Title>Navigation</Drawer.Title>                </Drawer.Header>                <Drawer.Body>                  <nav {...stylex.props(styles.navigation)}>                    {navItems.map((item) => (                      <button key={item.label} {...stylex.props(styles.navButton)} type="button">                        <item.icon {...stylex.props(styles.icon)} />                        {item.label}                      </button>                    ))}                  </nav>                </Drawer.Body>              </Drawer.Content>            </Drawer.Popup>          </Drawer.Viewport>        </Drawer.Portal>      </Drawer>    </Drawer.Provider>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

遮罩变体

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 drawer-backdrop-variants (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { Button, CloseIcon, Drawer } from "@lenso/ui";import { styles } from "./styles";
export function BackdropVariants() {  const variants = ["opaque", "blur", "transparent"] as const;  return (    <div {...stylex.props(styles.row)}>      {variants.map((variant) => (        <Drawer.Provider key={variant}>          <Drawer swipeDirection="down">            <Drawer.Trigger render={<Button variant="secondary" />}>              {variant.charAt(0).toUpperCase() + variant.slice(1)}            </Drawer.Trigger>            <Drawer.Portal>              <Drawer.Backdrop variant={variant} />              <Drawer.Viewport>                <Drawer.Popup>                  <Drawer.Content>                    <Drawer.Handle />                    <Drawer.Close                      aria-label="Close drawer"                      render={                        <Button                          isIconOnly                          size="sm"                          variant="tertiary"                          xstyle={styles.cornerClose}                        />                      }                    >                      <CloseIcon />                    </Drawer.Close>                    <Drawer.Header>                      <Drawer.Title>                        Backdrop: {variant.charAt(0).toUpperCase() + variant.slice(1)}                      </Drawer.Title>                    </Drawer.Header>                    <Drawer.Body>                      <p>                        This drawer uses the <code>{variant}</code> backdrop variant.                      </p>                    </Drawer.Body>                    <Drawer.Footer>                      <Drawer.Close render={<Button xstyle={styles.fullWidth} />}>                        Close                      </Drawer.Close>                    </Drawer.Footer>                  </Drawer.Content>                </Drawer.Popup>              </Drawer.Viewport>            </Drawer.Portal>          </Drawer>        </Drawer.Provider>      ))}    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

自定义样式

Tailwind CSS

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
// Adapted from HeroUI v3.2.6 drawer-custom-styles (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { Button, Drawer } from "@lenso/ui";import { styles } from "./styles";
export function CustomStyles() {  return (    <Drawer.Provider>      <Drawer swipeDirection="right">        <Drawer.Trigger render={<Button variant="secondary" />}>Open filters</Drawer.Trigger>        <Drawer.Portal>          <Drawer.Backdrop variant="blur" />          <Drawer.Viewport>            <Drawer.Popup xstyle={styles.filters}>              <Drawer.Content>                <Drawer.Header>                  <Drawer.Title>Filters</Drawer.Title>                </Drawer.Header>                <Drawer.Body>                  <p {...stylex.props(styles.muted)}>Narrow results by status, owner, or date.</p>                </Drawer.Body>                <Drawer.Footer>                  <Drawer.Close render={<Button variant="secondary" />}>Cancel</Drawer.Close>                  <Drawer.Close render={<Button />}>Apply</Drawer.Close>                </Drawer.Footer>              </Drawer.Content>            </Drawer.Popup>          </Drawer.Viewport>        </Drawer.Portal>      </Drawer>    </Drawer.Provider>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

全局 CSS

可使用 @layer components 指令自定义 Drawer 组件类。

了解更多。

@layer components {  .drawer__backdrop {    @apply bg-gradient-to-br from-black/50 to-black/70;  }
  .drawer__dialog {    @apply rounded-2xl border border-white/10 shadow-2xl;  }
  .drawer__header {    @apply text-center;  }
  .drawer__close-trigger {    @apply rounded-full bg-white/10 hover:bg-white/20;  }}

样式参考

HeroUI 遵循 BEM 方法论,确保组件变体与状态可复用且易于自定义。

CSS 类

Drawer 组件使用以下 CSS 类(查看源码样式):

基础类 [!toc]

  • .drawer__trigger - 打开抽屉的触发元素
  • .drawer__backdrop - 抽屉背后的遮罩层
  • .drawer__content - 抽屉面板的定位包裹层
  • .drawer__dialog - 抽屉面板本身
  • .drawer__header - 标题区域
  • .drawer__heading - 主标题文本
  • .drawer__body - 主可滚动内容区域
  • .drawer__footer - 操作按钮区域
  • .drawer__handle - 视觉拖拽手柄指示器
  • .drawer__close-trigger - 关闭按钮元素

遮罩变体 [!toc]

  • .drawer__backdrop--opaque - 不透明彩色遮罩(默认)
  • .drawer__backdrop--blur - 模糊遮罩,玻璃效果
  • .drawer__backdrop--transparent - 透明遮罩(无覆盖层)

位置变体 [!toc]

  • .drawer__content--bottom - 从底部边缘滑入(默认)
  • .drawer__content--top - 从顶部边缘滑入
  • .drawer__content--left - 从左侧边缘滑入
  • .drawer__content--right - 从右侧边缘滑入

对话框变体 [!toc]

  • .drawer__dialog--top - 从顶部边缘滑入
  • .drawer__dialog--bottom - 从底部边缘滑入
  • .drawer__dialog--left - 从左侧边缘滑入
  • .drawer__dialog--right - 从右侧边缘滑入

交互状态

组件支持以下交互状态:

  • Focus::focus-visible 或 [data-focus-visible="true"] - 应用于触发器与关闭按钮
  • Hover::hover 或 [data-hovered="true"] - 悬停时应用于关闭按钮
  • Active::active 或 [data-pressed="true"] - 按下时应用于触发器与关闭按钮
  • Entering:[data-entering] - 抽屉打开动画期间
  • Exiting:[data-exiting] - 抽屉关闭动画期间
  • Placement:[data-placement="*"] - 根据抽屉位置(top、bottom、left、right)应用

API 参考

Drawer

Prop类型默认值描述
childrenReactNode-触发器与 backdrop 元素
stateUseOverlayStateReturn-受控 overlay 状态

Drawer.Trigger

Prop类型默认值描述
childrenReactNode-自定义触发器内容
classNamestring-CSS 类

Drawer.Backdrop

Prop类型默认值描述
variant"opaque" | "blur" | "transparent""opaque"遮罩覆盖层样式
isDismissablebooleantrue点击 backdrop 关闭
isKeyboardDismissDisabledbooleanfalse禁用 ESC 关闭
isOpenboolean-受控打开状态
onOpenChange(isOpen: boolean) => void-打开状态变化处理函数
classNamestring | (values) => string-Backdrop CSS 类

Drawer.Content

Prop类型默认值描述
placement"top" | "bottom" | "left" | "right""bottom"抽屉滑入的边缘
classNamestring | (values) => string-Content CSS 类

Drawer.Dialog

Prop类型默认值描述
childrenReactNode-Dialog 内容
classNamestring-CSS 类
rolestring"dialog"ARIA role
aria-labelstring-无障碍标签
aria-labelledbystring-标签元素 ID

Drawer.Header

Prop类型默认值描述
childrenReactNode-Header 内容
classNamestring-CSS 类

Drawer.Heading

Prop类型默认值描述
childrenReactNode-标题文本
classNamestring-CSS 类

Drawer.Body

Prop类型默认值描述
childrenReactNode-Body 内容
classNamestring-CSS 类

Drawer.Footer

Prop类型默认值描述
childrenReactNode-Footer 内容
classNamestring-CSS 类

Drawer.Handle

Prop类型默认值描述
classNamestring-CSS 类

Drawer.CloseTrigger

Prop类型默认值描述
childrenReactNode-自定义关闭按钮
classNamestring | (values) => string-CSS 类

useOverlayState Hook

import { useOverlayState } from "@lenso/ui";
const state = useOverlayState({  defaultOpen: false,  onOpenChange: (isOpen) => console.log(isOpen),});
state.isOpen; // Current statestate.open(); // Open drawerstate.close(); // Close drawerstate.toggle(); // Toggle statestate.setOpen(); // Set state directly

无障碍

实现 WAI-ARIA Dialog 模式:

  • 焦点陷阱:打开时焦点锁定在抽屉内
  • 键盘:ESC 关闭(可关闭时),Tab 循环元素
  • 屏幕阅读器:通过 React Aria 提供正确的 ARIA 属性
  • 滚动锁定:打开时禁用 body 滚动
  • 拖拽关闭:在 handle、header 和 footer 区域支持指针拖拽手势

相关组件