Skip to content

feat: add resizable drawer feature #528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

QdabuliuQ
Copy link

@QdabuliuQ QdabuliuQ commented Aug 13, 2025

🤔 This is a ...

  • 🆕 New feature
  • 🐞 Bug fix
  • 📝 Site / documentation improvement
  • 📽️ Demo improvement
  • 💄 Component style improvement
  • 🤖 TypeScript definition improvement
  • 📦 Bundle size optimization
  • ⚡️ Performance optimization
  • ⭐️ Feature enhancement
  • 🌐 Internationalization
  • 🛠 Refactoring
  • 🎨 Code style optimization
  • ✅ Test Case
  • 🔀 Branch merge
  • ⏩ Workflow
  • ⌨️ Accessibility improvement
  • ❓ Other (about what?)

🔗 Related Issues

feat: ant-design/ant-design#54597

💡 Background and Solution

add resizable feature to Drawer component
add ResizableLine component to control the size of drawer

📝 Change Log

Language Changelog
🇺🇸 English add resizable feature to Drawer component
🇨🇳 Chinese Drawer 组件添加 resizable 属性

Summary by CodeRabbit

  • 新功能
    • 抽屉支持可拖拽调整尺寸(resizable),含开始/进行/结束回调;适配左右/上下方位,提供可见拖拽边与光标反馈,拖拽中抑制动画以提升流畅度。
    • 新增上下方向的过渡动效;统一多方位与无遮罩等样式表现。
  • 文档
    • README 更新并补充 resizable 属性说明。
    • 新增“Resizable”演示页面与示例,支持切换方位与开关可调整。
  • 测试
    • 新增可调整尺寸的交互用例。

Copy link

vercel bot commented Aug 13, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Project Deployment Preview Comments Updated (UTC)
drawer Ready Preview Comment Aug 13, 2025 0:12am

Copy link

coderabbitai bot commented Aug 13, 2025

Walkthrough

在 Drawer 组件中引入可调整尺寸功能:新增 resizable 与相关回调 API,添加 ResizableLine 组件与样式,更新 DrawerPopup 以支持四方向拖拽调整宽高;补充示例、文档与测试;导出 Placement 类型;调整动画与样式结构,并更新 tsconfig 包含范围。

Changes

Cohort / File(s) Summary
Public API 与类型
src/Drawer.tsx, src/DrawerPopup.tsx, src/inter.ts, src/index.ts
DrawerProps 增加 resizable、onResize/Start/End;DrawerPopup 接口同步;classNames/styles 增加 resizableLine;index 额外导出 Placement。
可调整尺寸组件
src/ResizableLine.tsx
新增可拖拽 ResizableLine 组件,处理鼠标事件、方向与尺寸约束,发出开始/过程中/结束回调。
样式与动效
assets/index.less, docs/examples/assets/motion.less
重构 Drawer 样式到 prefixCls 体系;新增 resizable-line 样式与四向光标;扩展 motion 为 top/bottom 方向。
文档与示例
README.md, docs/demo/resizable.md, docs/examples/resizable.tsx, docs/examples/getContainer-false.tsx
README 增加 resizable 属性说明;新增“Resizable”文档页与完整示例;示例用法加入 resizable。
测试
tests/index.spec.tsx
新增“should support resizable”用例,模拟拖拽并断言尺寸变化。
配置
tsconfig.json
从 include 移除 .dumi 目录。

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Drawer as Drawer
  participant Popup as DrawerPopup
  participant Line as ResizableLine
  participant DOM as Wrapper(Ref)

  User->>Drawer: open + resizable
  Drawer->>Popup: props {..., resizable, onResize*}
  Popup->>DOM: measure wrapper (maxSize)
  Popup-->>User: render with size style

  User->>Line: mousedown (edge)
  Line->>Popup: onResizeStart()
  Popup-->>User: suppress motion while dragging

  User->>Line: mousemove (delta)
  Line->>Popup: onResize(newSize)
  Popup->>DOM: apply width/height = newSize

  User->>Line: mouseup
  Line->>Popup: onResizeEnd(finalSize)
  Popup-->>User: restore motion class
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Suggested reviewers

  • zombieJ

Poem

小兔拉线轻轻移,
抽屉边缘起涟漪。
左右上下随心意,
像风量尺量晨曦。
咔哒一松尺寸定,
代码微笑藏草地。 🐇📐

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @QdabuliuQ, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant new feature to the Drawer component, allowing it to be resizable. Users can now dynamically adjust the width or height of the drawer by dragging its edges, providing greater flexibility in UI layouts. This enhancement includes new properties to enable the feature and provides callbacks for managing the resizing process, along with dedicated styling and a new internal component to handle the drag interactions. The changes are fully documented and include a new example for easy adoption.

Highlights

  • Resizable Drawer Feature: The Drawer component now supports a resizable prop, which when set to true, allows users to change the drawer's size by dragging its edges. This enhances user interaction and flexibility.
  • New Resize Event Callbacks: Three new callback functions, onResize, onResizeStart, and onResizeEnd, have been added to the Drawer component. These provide developers with hooks to execute custom logic during different stages of the resizing process, such as updating state or performing validations.
  • Introduction of ResizableLine Component: A dedicated ResizableLine component has been introduced to encapsulate the logic for handling mouse events (mousedown, mousemove, mouseup) and calculating the new size of the drawer during a drag operation. This modular approach keeps the core Drawer component cleaner.
  • Styling for Resizable Handles: New CSS rules have been added to style the resizable line, including visual feedback for hover and dragging states. This ensures a clear and intuitive user experience when interacting with the resize handles.
  • Documentation and Example Updates: Comprehensive updates to the documentation and a new interactive example have been provided. This allows other developers to quickly understand how to implement and utilize the new resizable drawer feature in their applications.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a resizable drawer feature, which is a great addition. The implementation is mostly solid, with a new ResizableLine component handling the drag logic and updates to the Drawer and DrawerPopup components to support it. I've found a few issues: an incorrect CSS value for overflow, a bug in the animation styles for top/bottom placements, and an API inconsistency in the new resize event handlers. The documentation and examples have been updated accordingly. My review includes suggestions to fix these issues.

Comment on lines +348 to +352
if (resizable) {
style.overflow = 'none';
} else {
style.overflow = 'auto';
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The CSS overflow property does not have a value 'none'. To allow the resize handle to be visible, you should use 'visible'. Additionally, when not resizable, the overflow should probably be 'hidden' to match the original behavior and prevent the wrapper from becoming scrollable, as only the inner content is designed to scroll.

    if (resizable) {
      style.overflow = 'visible';
    } else {
      style.overflow = 'hidden';
    }

Comment on lines +14 to +15
onResizeEnd?: (size: number) => void;
onResizeStart?: (size: number) => void;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The types for onResizeEnd and onResizeStart should be () => void to match the Drawer component's public API. The current types which accept a size argument are inconsistent with the parent components.

  onResizeEnd?: () => void;
  onResizeStart?: () => void;

const rect = parentElement.getBoundingClientRect();
const currentSize = isHorizontal ? rect.width : rect.height;
setStartSize(currentSize);
onResizeStart?.(currentSize);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To align with the public API, onResizeStart should be called without arguments.

        onResizeStart?.();

if (parentElement) {
const rect = parentElement.getBoundingClientRect();
const finalSize = isHorizontal ? rect.width : rect.height;
onResizeEnd?.(finalSize);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To align with the public API, onResizeEnd should be called without arguments.

        onResizeEnd?.();

transform: translateY(-100%);

&-active {
transform: translateX(0);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The transform property here should be translateY(0) to match the vertical animation. Currently, it's translateX(0), which is incorrect for a drawer animating from the top.

        transform: translateY(0);

transform: translateY(100%);

&-active {
transform: translateX(0);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the top placement animation, the transform property here should be translateY(0) for the bottom placement animation to work correctly.

        transform: translateY(0);

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🔭 Outside diff range comments (1)
src/inter.ts (1)

1-13: 命名不一致:接口使用 section,文档与样式使用 content,建议新增别名并标注弃用

DrawerClassNames/DrawerStyles 中是 section,但 README 与样式体系(如 -content)更贴近 “content”。为避免使用者困惑与类型不匹配,建议在类型上新增 content,并将 section 标注为 deprecated,保证向后兼容。

建议变更:

 export interface DrawerClassNames {
   mask?: string;
   wrapper?: string;
-  section?: string;
+  /** @deprecated 请使用 `content` */
+  section?: string;
+  content?: string;
   resizableLine?: string;
 }

 export interface DrawerStyles {
-  mask?: React.CSSProperties;
-  wrapper?: React.CSSProperties;
-  section?: React.CSSProperties;
+  mask?: React.CSSProperties;
+  wrapper?: React.CSSProperties;
+  /** @deprecated 请使用 `content` */
+  section?: React.CSSProperties;
+  content?: React.CSSProperties;
   resizableLine?: React.CSSProperties;
 }

(如代码中有读取 section 的实现,建议同时支持 content ?? section。)

🧹 Nitpick comments (18)
tsconfig.json (1)

16-16: 明确排除 .dumi 目录,避免被通配重新包含

当前 include 使用 "/*.ts"、"/*.tsx" 会再次把 .dumi 目录纳入编译范围。若期望排除 .dumi(以及构建产物),建议补充 exclude,提高 ts 编译性能并避免噪音类型报错。

建议变更:

-  "include": [".dumirc.ts", "**/*.ts", "**/*.tsx"]
+  "include": [".dumirc.ts", "**/*.ts", "**/*.tsx"],
+  "exclude": [".dumi", "node_modules", "dist"]
src/inter.ts (1)

5-5: 新增 resizableLine 类型后请同步 README 的 classNames/styles 说明

接口已增加 resizableLine?: stringresizableLine?: React.CSSProperties,但 README 的 API 表格中 classNamesstyles 的对象结构未包含该键,文档与实现不一致,建议同步更新文档。

我可以附带一段 README 的表格更新 diff,是否需要我起一个跟进 PR?

Also applies to: 12-12

README.md (3)

47-48: 补充 resizableLine 的类型说明,保持与代码一致

classNames/styles 的类型签名未包含 resizableLine,与 src/inter.ts 定义不一致,建议补上:

建议变更(文档示意):

-| classNames      | { mask?: string; content?: string; wrapper?: string; }                      | -                                      | pass className to target area                                                 |
-| styles          | { mask?: CSSProperties; content?: CSSProperties; wrapper?: CSSProperties; } | -                                      | pass style to target area                                                     |
+| classNames      | { mask?: string; content?: string; wrapper?: string; resizableLine?: string; }                      | - | pass className to target area |
+| styles          | { mask?: CSSProperties; content?: CSSProperties; wrapper?: CSSProperties; resizableLine?: CSSProperties; } | - | pass style to target area |

55-55: 文档类型更精确:placement 可标注为联合类型

代码侧已导出 Placement 类型(如 PR 摘要所述),文档仍是 string。建议文档同步为更精确的联合类型('left' | 'right' | 'top' | 'bottom' 或引用 Placement),提升消费者可读性。


59-59: 拼写错误:React.createElement

React.creactElement 应为 React.createElement

建议变更:

- | getContainer    | string \| func \| HTMLElement                                               | `body`                                 | Return the mount node for Drawer. if is `null` use React.creactElement        |
+ | getContainer    | string \| func \| HTMLElement                                               | `body`                                 | Return the mount node for Drawer. If it is `null`, use React.createElement    |
assets/index.less (3)

62-107: 提升可用性:防止拖拽时选中文本与多指缩放干扰

建议为可拖拽线增加 user-select: nonetouch-action: none,在移动端和触摸板上会更稳。

建议变更:

   &-resizable-line {
     position: absolute;
     z-index: 2;
     pointer-events: auto;
+    user-select: none;
+    touch-action: none;

68-74: 避免硬编码品牌色,改为可配置变量

#1890ff 是 Ant Design v4 的主色,rc-drawer 作为独立库建议使用可配置变量,避免与主题脱钩。可新增局部变量,默认值保留原色以兼容。

建议变更:

+@resizer-active-color: #1890ff; // 可按需覆写
 ...
   &-resizable-line {
@@
-    &:hover {
-      background-color: #1890ff !important;
-    }
+    &:hover {
+      background-color: @resizer-active-color !important;
+    }
@@
-    &-dragging {
-      background-color: #1890ff !important;
-    }
+    &-dragging {
+      background-color: @resizer-active-color !important;
+    }

110-117: z-index 定义重复且数值不一致,建议统一

本文件前部 .@{prefixCls} 已设置 z-index: @zIndex(当前为 1050),此处再次设置为 9999。两处相同选择器的重复定义会以较晚出现者为准,建议统一到变量,避免维护困惑与潜在层级问题。

建议其一(更易维护):改为使用变量

-  z-index: 9999;
+  z-index: @zIndex;

建议其二(若确需抬高层级):统一在顶部提升变量值

-@zIndex: 1050;
+@zIndex: 9999;

请确认期望行为,并选择其一。

docs/demo/resizable.md (1)

2-2: 标题大小写与导航不一致,建议统一

Front matter 中的 title 为小写“resizable”,而 nav.title 为“Resizable”。为保持文档一致性与站点导航显示统一,建议首字母大写。

-title: resizable
+title: Resizable
tests/index.spec.tsx (1)

479-516: 增强用例稳定性并覆盖回调行为断言

  • 使用 getContainer={false},减少与 Portal/文档滚动相关的偶发性影响。
  • 将拖拽序列包裹在 act 中,避免 React 状态更新告警。
  • 覆盖 onResize/onResizeStart/onResizeEnd 回调是否触发及参数正确性的断言,提升行为验证完整性。
 it('should support resizable', () => {
-  const { unmount } = render(
-    <Drawer resizable open placement="left" width={200} />,
-  );
+  const onResizeStart = jest.fn();
+  const onResize = jest.fn();
+  const onResizeEnd = jest.fn();
+  const { unmount } = render(
+    <Drawer
+      resizable
+      open
+      placement="left"
+      width={200}
+      getContainer={false}
+      onResizeStart={onResizeStart}
+      onResize={onResize}
+      onResizeEnd={onResizeEnd}
+    />,
+  );

   // Mock getBoundingClientRect for the content wrapper to simulate real DOM dimensions
   const contentWrapper = document.querySelector(
     '.rc-drawer-content-wrapper',
   ) as HTMLElement;
   const mockGetBoundingClientRect = jest.fn(
     () =>
       ({
         width: 200,
         height: 400,
         top: 0,
         left: 0,
         bottom: 400,
         right: 200,
         x: 0,
         y: 0,
         toJSON: () => ({}),
       }) as DOMRect,
   );
   contentWrapper.getBoundingClientRect = mockGetBoundingClientRect;

   const resizableLine = document.querySelector('.rc-drawer-resizable-line');
   expect(resizableLine).toBeTruthy();

-  // Simulate drag from 200px to 100px (should reduce width by 100px)
-  fireEvent.mouseDown(resizableLine, { clientX: 200 });
-  fireEvent.mouseMove(document, { clientX: 100, clientY: 0 });
-  fireEvent.mouseUp(document, { clientX: 100, clientY: 0 });
+  // Simulate drag from 200px to 100px (should reduce width by 100px)
+  act(() => {
+    fireEvent.mouseDown(resizableLine!, { clientX: 200 });
+    fireEvent.mouseMove(document, { clientX: 100, clientY: 0 });
+    fireEvent.mouseUp(document, { clientX: 100, clientY: 0 });
+  });

   expect(document.querySelector('.rc-drawer-content-wrapper')).toHaveStyle({
     width: '100px',
   });
+  expect(onResizeStart).toHaveBeenCalledTimes(1);
+  expect(onResize).toHaveBeenCalled();
+  // 最后一笔应为 100 宽度(或高度,取决于 placement)
+  expect(onResize).toHaveBeenLastCalledWith(100);
+  expect(onResizeEnd).toHaveBeenCalledTimes(1);
   unmount();
 });
docs/examples/resizable.tsx (3)

8-8: 建议优化类型导入路径

从'@/index'导入类型可能导致构建路径解析问题。建议使用相对路径或明确的包名导入。

-import type { Placement } from '@/index';
+import type { Placement } from 'rc-drawer';

42-42: key属性可能导致不必要的重新挂载

使用placement作为key会在每次placement变化时强制重新挂载Drawer组件,这可能不是期望的行为。考虑移除key属性或使用更稳定的标识符。

-key={placement}

47-52: 改进示例文案的用户体验

文案可以更清晰地说明调整大小的操作方式。

-<p>
-  You can drag the drawer edge to resize (only works when
-  &quot;Resizable&quot; is checked).
-</p>
+<p>
+  拖拽抽屉边缘可调整大小(仅在勾选"Resizable"时有效)。
+</p>
src/ResizableLine.tsx (4)

24-24: 重新考虑minSize默认值

默认minSize为100可能过大,特别是对于移动端或小屏幕设备。建议设置更小的默认值或根据方向动态调整。

-minSize = 100,
+minSize = 48,

82-84: maxSize验证逻辑可以简化

当前的maxSize检查有些冗余,可以简化条件判断。

-// Only apply maxSize if it's a valid positive number
-if (maxSize !== undefined && maxSize > 0 && newSize > maxSize) {
+if (maxSize && newSize > maxSize) {

100-112: handleMouseUp中的重复代码

与handleMouseDown中的父元素获取逻辑重复,建议提取为共用函数。

+const getParentSize = React.useCallback(() => {
+  const parentElement = lineRef.current?.parentElement;
+  if (!parentElement) return 0;
+  const rect = parentElement.getBoundingClientRect();
+  return isHorizontal ? rect.width : rect.height;
+}, [isHorizontal]);

 const handleMouseUp = React.useCallback(() => {
   if (isDragging) {
     onDraggingChange?.(false);
-    // Get the final size after resize
-    const parentElement = lineRef.current?.parentElement;
-    if (parentElement) {
-      const rect = parentElement.getBoundingClientRect();
-      const finalSize = isHorizontal ? rect.width : rect.height;
+    const finalSize = getParentSize();
+    if (finalSize > 0) {
       onResizeEnd?.(finalSize);
-    }
   }
-}, [isDragging, onResizeEnd, isHorizontal, onDraggingChange]);
+}, [isDragging, onResizeEnd, onDraggingChange, getParentSize]);

135-140: resizeLineStyle可以优化

style对象的构建可以更高效,避免每次渲染时创建新对象。

-const resizeLineStyle: React.CSSProperties = {
-  position: 'absolute',
-  zIndex: 2,
-  cursor: isHorizontal ? 'col-resize' : 'row-resize',
-  ...style,
-};
+const resizeLineStyle = React.useMemo<React.CSSProperties>(() => ({
+  position: 'absolute',
+  zIndex: 2,
+  cursor: isHorizontal ? 'col-resize' : 'row-resize',
+  ...style,
+}), [isHorizontal, style]);
src/DrawerPopup.tsx (1)

297-301: 状态初始化可以优化

建议为currentSize提供更明确的初始值,而不是undefined。

-const [currentSize, setCurrentSize] = React.useState<number>();
+const [currentSize, setCurrentSize] = React.useState<number | undefined>(undefined);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2418e05 and 1545c74.

📒 Files selected for processing (13)
  • README.md (1 hunks)
  • assets/index.less (2 hunks)
  • docs/demo/resizable.md (1 hunks)
  • docs/examples/assets/motion.less (1 hunks)
  • docs/examples/getContainer-false.tsx (1 hunks)
  • docs/examples/resizable.tsx (1 hunks)
  • src/Drawer.tsx (3 hunks)
  • src/DrawerPopup.tsx (5 hunks)
  • src/ResizableLine.tsx (1 hunks)
  • src/index.ts (1 hunks)
  • src/inter.ts (1 hunks)
  • tests/index.spec.tsx (1 hunks)
  • tsconfig.json (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
docs/examples/resizable.tsx (4)
docs/examples/getContainer-false.tsx (1)
  • open (21-114)
src/index.ts (1)
  • Placement (5-5)
src/Drawer.tsx (1)
  • Placement (15-15)
src/DrawerPopup.tsx (1)
  • Placement (26-26)
src/ResizableLine.tsx (3)
src/index.ts (1)
  • Placement (5-5)
src/Drawer.tsx (1)
  • Placement (15-15)
src/DrawerPopup.tsx (1)
  • Placement (26-26)
🔇 Additional comments (11)
README.md (1)

67-76: LGTM:新增 resizable 与 resize 回调文档与实现一致

新文档项与 PR 目标一致,描述清晰。

assets/index.less (1)

33-35: 确认移除 overflow: hidden 的影响

content-wrapper 注释掉 overflow: hidden 能让拖拽线溢出 3px 可点,但同时也可能导致内容溢出时出现视觉残留。确认是否在拖拽中通过行内样式或状态条件控制 overflow,以避免滚动条/阴影露出。

docs/examples/getContainer-false.tsx (1)

55-56: 示例新增 resizable 用法,LGTM

在 getContainer={false} 的场景展示可拖拽调整尺寸是合理的,变更清晰可读。

src/index.ts (1)

3-5: 导出 Placement 类型,LGTM

类型面向外部暴露更完整,便于下游消费与类型提示。

src/Drawer.tsx (2)

56-59: 解构新增回调以便传递,LGTM

onResize/onResizeStart/onResizeEnd 在此解构后下传,符合预期的数据流。


120-123: 回调透传到 DrawerPopup,LGTM

事件处理集合中纳入 resize 系列回调,保持 API 一致性与集中传递,合理。

src/DrawerPopup.tsx (5)

80-84: LGTM!可调整尺寸相关的API设计合理

新增的resizable相关属性和回调函数API设计清晰,类型定义完整。


397-397: 动画类名条件逻辑很好

在拖拽时禁用动画类名是明智的设计,避免拖拽过程中的动画干扰。


406-420: ResizableLine组件集成正确

ResizableLine的属性传递和事件处理集成得很好,maxSize和isDragging状态管理恰当。


393-393: wrapperRef 使用验证通过

已在 src/DrawerPopup.tsx 中确认 wrapperRef 的定义与使用场景一致:

  • 定义为 React.useRef<HTMLDivElement>(null)
  • calculateMaxSize 回调及 JSX 中通过 ref={wrapperRef} 正确绑定至 DOM 元素

未发现引用不一致或遗漏,使用正常。


304-313: calculateMaxSize函数需要添加边界检查

函数缺少对rect的null检查,可能导致运行时错误。

 const calculateMaxSize = React.useCallback(() => {
   if (wrapperRef.current) {
     const rect = wrapperRef.current.parentElement?.getBoundingClientRect();
-    const newMaxSize =
+    if (!rect) return;
+    const newMaxSize =
       placement === 'left' || placement === 'right'
-        ? (rect?.width ?? 0)
-        : (rect?.height ?? 0);
+        ? rect.width
+        : rect.height;
     setMaxSize(newMaxSize);
   }
 }, [placement]);

Likely an incorrect or invalid review comment.

Comment on lines +87 to +127
&-top {
.panel-motion();

&-enter,
&-appear {
transform: translateY(-100%);

&-active {
transform: translateX(0);
}
}

&-leave {
transform: translateX(0);

&-active {
transform: translateY(-100%) !important;
}
}
}

&-bottom {
.panel-motion();

&-enter,
&-appear {
transform: translateY(100%);

&-active {
transform: translateX(0);
}
}

&-leave {
transform: translateX(0);

&-active {
transform: translateY(100%) !important;
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

动画方向错误:top/bottom 使用了 translateX,应为 translateY

topbottom 的进入/离开状态应在 Y 轴上过渡,当前 &-enter,&-appear&-active 以及 &-leave 的静态状态误用了 translateX(0),会导致方向错乱的动画。

建议修复:

   &-top {
     .panel-motion();

     &-enter,
     &-appear {
       transform: translateY(-100%);

       &-active {
-        transform: translateX(0);
+        transform: translateY(0);
       }
     }

     &-leave {
-      transform: translateX(0);
+      transform: translateY(0);

       &-active {
         transform: translateY(-100%) !important;
       }
     }
   }

   &-bottom {
     .panel-motion();

     &-enter,
     &-appear {
       transform: translateY(100%);

       &-active {
-        transform: translateX(0);
+        transform: translateY(0);
       }
     }

     &-leave {
-      transform: translateX(0);
+      transform: translateY(0);

       &-active {
         transform: translateY(100%) !important;
       }
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
&-top {
.panel-motion();
&-enter,
&-appear {
transform: translateY(-100%);
&-active {
transform: translateX(0);
}
}
&-leave {
transform: translateX(0);
&-active {
transform: translateY(-100%) !important;
}
}
}
&-bottom {
.panel-motion();
&-enter,
&-appear {
transform: translateY(100%);
&-active {
transform: translateX(0);
}
}
&-leave {
transform: translateX(0);
&-active {
transform: translateY(100%) !important;
}
}
}
&-top {
.panel-motion();
&-enter,
&-appear {
transform: translateY(-100%);
&-active {
transform: translateY(0);
}
}
&-leave {
transform: translateY(0);
&-active {
transform: translateY(-100%) !important;
}
}
}
&-bottom {
.panel-motion();
&-enter,
&-appear {
transform: translateY(100%);
&-active {
transform: translateY(0);
}
}
&-leave {
transform: translateY(0);
&-active {
transform: translateY(100%) !important;
}
}
}
🤖 Prompt for AI Agents
In docs/examples/assets/motion.less around lines 87 to 127, the top/bottom
panels incorrectly use translateX(0) for Y-axis transitions; change the
transform values used in both blocks so that the &-enter/&-appear &-active rules
use transform: translateY(0); and the &-leave static transform: translateY(0);
(preserve the existing !important on the &-leave &-active rules), ensuring all
translateX(0) occurrences in these top/bottom sections become translateY(0).


export default () => {
const [open, setOpen] = React.useState(false);
const [placement, setPlacement] = React.useState('right');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

placement状态类型缺少类型注解

placement状态应该明确指定为Placement类型以确保类型安全。

-const [placement, setPlacement] = React.useState('right');
+const [placement, setPlacement] = React.useState<Placement>('right');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [placement, setPlacement] = React.useState('right');
const [placement, setPlacement] = React.useState<Placement>('right');
🤖 Prompt for AI Agents
In docs/examples/resizable.tsx around line 12, the placement state lacks a type
annotation; update the declaration to explicitly type the state as Placement
(e.g. use React.useState<Placement>('right')) and ensure the Placement type is
imported or referenced from the UI library/module that defines it so the state
is type-safe and setPlacement has the correct typed parameter.

Comment on lines +30 to +35
<select value={placement} onChange={e => setPlacement(e.target.value)}>
<option value="left">Left</option>
<option value="right">Right</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
</select>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

select元素缺少类型安全的onChange处理

setPlacement接受string类型但应该确保只能是Placement类型的值。

-<select value={placement} onChange={e => setPlacement(e.target.value)}>
+<select value={placement} onChange={e => setPlacement(e.target.value as Placement)}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<select value={placement} onChange={e => setPlacement(e.target.value)}>
<option value="left">Left</option>
<option value="right">Right</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
</select>
<select
value={placement}
onChange={e => setPlacement(e.target.value as Placement)}
>
<option value="left">Left</option>
<option value="right">Right</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
</select>
🤖 Prompt for AI Agents
In docs/examples/resizable.tsx around lines 30 to 35, the select onChange is not
type-safe because e.target.value is a plain string while setPlacement expects a
Placement; update the handler to use the correct event type and narrow/cast the
value to Placement (for example define the handler as (e:
React.ChangeEvent<HTMLSelectElement>) => setPlacement(e.target.value as
Placement) or validate against a Placement union before calling setPlacement),
and ensure the Placement type is imported/used so only valid placement values
are passed.

src/Drawer.tsx Outdated
Comment on lines +29 to 34
/** Whether to enable width resize */
resizable?: boolean;
onResize?: (size: number) => void;
onResizeStart?: () => void;
onResizeEnd?: () => void;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

文档注释不准确,且需确认是否与 DrawerPopupProps 重复定义

  • “Whether to enable width resize” 仅描述了宽度,但 top/bottom 场景应为高度。建议描述统一为“是否允许通过拖拽调整尺寸(横向为宽度,纵向为高度)”。
  • DrawerProps 继承了 Omit<DrawerPopupProps, ...>。如果 DrawerPopupProps 已声明了 resizable/onResize/onResizeStart/onResizeEnd,则此处重复定义会造成维护冗余及潜在类型漂移风险。建议仅在一个地方(通常是 DrawerPopupProps)定义,再由 DrawerProps 继承暴露。

建议先更新注释;同时请执行以下脚本检查 DrawerPopupProps 是否已包含这些字段,避免重复定义:

应用注释修正示例:

-  /** Whether to enable width resize */
+  /** 是否允许通过拖拽调整尺寸(横向为宽度,纵向为高度) */

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== 查找 DrawerPopupProps 定义位置 =='
rg -n "interface\\s+DrawerPopupProps" -A 20

echo '== 在 DrawerPopupProps 中查找 resizable 相关字段 =='
rg -n "resizable\\s*\\?:" -n
rg -n "onResize(Start|End)?\\s*\\?:" -n

echo '== 在 Drawer.tsx 中定位重复字段 =='
rg -n "export interface DrawerProps" src/Drawer.tsx -n -A 40 | rg -n "resizable\\s*\\?|onResize(Start|End)?\\s*\\?:"

Length of output: 2149


统一注释 & 移除 DrawerProps 中重复定义

  • 文件:src/Drawer.tsx
  • 更新 resizable 的文档注释:
    - /** Whether to enable width resize */
    + /** 是否允许通过拖拽调整尺寸(横向为宽度,纵向为高度) */
  • src/DrawerPopup.tsx 已在 DrawerPopupProps 中声明了以下字段,无需在 DrawerProps 中重复定义,请移除:
    • resizable?: boolean
    • onResize?: (size: number) => void
    • onResizeStart?: () => void
    • onResizeEnd?: () => void

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/Drawer.tsx around lines 29 to 34, update the documentation for the
resizable prop to match the canonical comment used in DrawerPopup.tsx, and
remove the duplicated property declarations resizable?: boolean, onResize?:
(size: number) => void, onResizeStart?: () => void, and onResizeEnd?: () => void
from the DrawerProps interface (they are already declared in DrawerPopupProps);
keep the DrawerProps interface lean by deleting those four lines and ensure any
references/exports still resolve to the shared definitions in DrawerPopupProps.

Comment on lines +348 to +352
if (resizable) {
style.overflow = 'none';
} else {
style.overflow = 'auto';
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

overflow样式值不正确

'none'不是CSS overflow属性的有效值,应该使用'hidden'。

 if (resizable) {
-  style.overflow = 'none';
+  style.overflow = 'hidden';
 } else {
   style.overflow = 'auto';
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (resizable) {
style.overflow = 'none';
} else {
style.overflow = 'auto';
}
if (resizable) {
style.overflow = 'hidden';
} else {
style.overflow = 'auto';
}
🤖 Prompt for AI Agents
In src/DrawerPopup.tsx around lines 348 to 352, the code sets style.overflow =
'none' when resizable is true but 'none' is not a valid CSS overflow value;
change it to 'hidden' so the branch reads style.overflow = 'hidden' when
resizable is true (keep the else branch as 'auto'); ensure the value is a string
matching CSS overflow and types (if using TypeScript, it will accept 'hidden').

Comment on lines +38 to +61
const handleMouseDown = React.useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();

onDraggingChange?.(true);

if (isHorizontal) {
setStartPos(e.clientX);
} else {
setStartPos(e.clientY);
}

// Get the current size of the parent container
const parentElement = lineRef.current?.parentElement;
if (parentElement) {
const rect = parentElement.getBoundingClientRect();
const currentSize = isHorizontal ? rect.width : rect.height;
setStartSize(currentSize);
onResizeStart?.(currentSize);
}
},
[isHorizontal, onResizeStart, onDraggingChange],
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

优化事件处理逻辑和性能

handleMouseDown函数可以进一步优化:1) 添加null检查;2) 优化依赖数组。

 const handleMouseDown = React.useCallback(
   (e: React.MouseEvent) => {
     e.preventDefault();
     e.stopPropagation();

     onDraggingChange?.(true);

-    if (isHorizontal) {
-      setStartPos(e.clientX);
-    } else {
-      setStartPos(e.clientY);
-    }
+    const pos = isHorizontal ? e.clientX : e.clientY;
+    setStartPos(pos);

     // Get the current size of the parent container
     const parentElement = lineRef.current?.parentElement;
-    if (parentElement) {
+    if (!parentElement) {
+      onDraggingChange?.(false);
+      return;
+    }
       const rect = parentElement.getBoundingClientRect();
       const currentSize = isHorizontal ? rect.width : rect.height;
       setStartSize(currentSize);
       onResizeStart?.(currentSize);
-    }
   },
-  [isHorizontal, onResizeStart, onDraggingChange],
+  [isHorizontal, onResizeStart, onDraggingChange],
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleMouseDown = React.useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
onDraggingChange?.(true);
if (isHorizontal) {
setStartPos(e.clientX);
} else {
setStartPos(e.clientY);
}
// Get the current size of the parent container
const parentElement = lineRef.current?.parentElement;
if (parentElement) {
const rect = parentElement.getBoundingClientRect();
const currentSize = isHorizontal ? rect.width : rect.height;
setStartSize(currentSize);
onResizeStart?.(currentSize);
}
},
[isHorizontal, onResizeStart, onDraggingChange],
);
const handleMouseDown = React.useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
onDraggingChange?.(true);
// unify axis-based coordinate selection
const pos = isHorizontal ? e.clientX : e.clientY;
setStartPos(pos);
// Get the current size of the parent container
const parentElement = lineRef.current?.parentElement;
if (!parentElement) {
// abort if we can’t measure
onDraggingChange?.(false);
return;
}
const rect = parentElement.getBoundingClientRect();
const currentSize = isHorizontal ? rect.width : rect.height;
setStartSize(currentSize);
onResizeStart?.(currentSize);
},
[isHorizontal, onResizeStart, onDraggingChange],
);
🤖 Prompt for AI Agents
In src/ResizableLine.tsx around lines 38 to 61: the handleMouseDown callback
should defensively null-check the event target/ref and tighten its dependency
list to avoid unnecessary re-creations; update the function to early-return if e
is falsy, ensure lineRef.current exists before accessing parentElement (skip
parent logic if missing), and include the stable setters (setStartPos,
setStartSize) and lineRef in the dependency array (or use a lint-safe pattern)
while keeping only truly changing values (isHorizontal, onResizeStart,
onDraggingChange) to prevent stale closures and extra re-renders.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant