Skip to content

Conversation

@aojunhao123
Copy link
Contributor

@aojunhao123 aojunhao123 commented Dec 26, 2025

Summary by CodeRabbit

发布说明

  • 新功能

    • 新增在对话框内嵌套图片预览的示例组件(示例可在文档中查看)。
  • 文档

    • 添加嵌套预览演示页面与示例代码。
  • 改进

    • 优化预览与模态框的 Esc 键交互,提升多层模态场景下的关闭行为。
    • 提高预览容器的层级显示(z-index 调整),改善遮挡问题。
  • 杂项

    • 更新项目配置:新增忽略规则与类型配置,测试覆盖新增交互场景。

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 26, 2025

@aojunhao123 is attempting to deploy a commit to the React Component Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Dec 26, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

将图片预览的 ESC 键处理从组件内部移至 Portal 层,调整 Portal 渲染与生命周期(autoDestroy),新增嵌套示例与测试,并对样式、依赖、配置等做小幅更新(.gitignore、tsconfig、assets、docs、package.json)。

Changes

Cohort / File(s) 变更摘要
核心预览逻辑
src/Preview/index.tsx
移除组件内 keydown ESC 处理;通过 Portal 的 onEsc 在顶层时调用 onClose;Portal 使用 open={portalRender && open}autoDestroy={false}
样式调整
assets/preview.less
为主预览容器 .@{prefixCls}-preview 添加 z-index: 1055
文档示例
docs/demo/nested.md, docs/examples/nested.tsx
新增嵌套示例页面与组件:在 Dialog 内使用 Image.PreviewGroup 的演示。
测试
tests/preview.test.tsx
新增测试 "Esc closes preview then modal",验证在 Dialog 内打开预览时,按 Esc 先关闭预览再关闭 Dialog 的行为顺序。
依赖与配置
package.json, tsconfig.json, .gitignore
更新 @rc-component/portal 版本并新增 @rc-component/dialog;tsconfig 添加 compilerOptions.types@testing-library/jest-dom, jest, node);.gitignore 添加 .vscode

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Component as Preview Component
    participant Portal
    participant Dialog as Outer Dialog

    rect rgb(220,235,255)
    Note right of Component: 打开预览流程
    User->>Component: 点击图片打开预览
    Component->>Portal: render open=true (portalRender && open)
    Portal->>Portal: 成为顶层层级
    end

    rect rgb(255,235,220)
    Note right of Portal: 按下 Escape 后的处理
    User->>Portal: 按下 Escape
    Portal->>Portal: isTopLayer()?
    alt Portal 是顶层
        Portal->>Component: 调用 onEsc -> onClose
        Component->>Portal: 将 open 设为 false(关闭预览)
    else Portal 不是顶层
        Portal->>Dialog: 事件继续传递给 Dialog(Dialog 自行处理)
    end
    end
Loading

Estimated code review effort

🎯 3 (中等复杂度) | ⏱️ ~20 分钟

Suggested reviewers

  • thinkasany

Poem

🐰 预览与对话层次分明,
Escape 键各自归位不纠缠,
Portal 守住顶部的那扇门,
点击图片心情轻又欢,
嵌套交互终于安然。

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed 代码变更完全满足linked issue #56083的要求:通过将Esc处理从Preview组件移至Portal层级,确保Esc仅关闭图片预览而不关闭外层Modal。
Out of Scope Changes check ✅ Passed 所有变更均与修复issue #56083直接相关,包括Preview组件的Esc处理重构、Portal配置更新、测试和配置文件调整,未发现超出scope的修改。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed PR title 'fix: optimize ESC key handling in nested portal scenarios' directly summarizes the main changes: refactoring ESC key handling from component level to Portal level to fix nested modal scenarios.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • 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

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @aojunhao123, 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 resolves a reported bug where image previews, when opened inside a modal, could suffer from incorrect layering and inconsistent Escape key behavior. The changes involve adjusting the styling to ensure the preview is always on top, and refactoring the Escape key logic to work seamlessly with nested components. A new demo and test case have been added to verify the fix and prevent regressions, enhancing the overall user experience for image previews in complex UI structures.

Highlights

  • Modal Image Preview Fix: Addressed an issue where image previews within modals might not display correctly or handle Escape key events as expected, particularly in nested contexts.
  • Z-index Adjustment: Increased the z-index of the image preview component to ensure it renders above other modal content, resolving potential layering issues.
  • Improved Escape Key Handling: Refactored the Escape key event listener to leverage the Portal component's onEsc prop, allowing for more precise control over closing the topmost active component (like an image preview or modal).
  • New Demo and Test Case: Introduced a new demo showcasing an image preview within a nested modal, along with a dedicated test case to validate the correct Escape key behavior for such scenarios.
  • Dependency Updates: Updated several @rc-component dependencies and added antd and @rc-component/dialog to dev dependencies, ensuring compatibility and leveraging newer features.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

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 by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

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 pull request 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.

@codecov
Copy link

codecov bot commented Dec 26, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.41%. Comparing base (d3985c4) to head (283014a).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #494   +/-   ##
=======================================
  Coverage   99.41%   99.41%           
=======================================
  Files          17       17           
  Lines         509      511    +2     
  Branches      152      153    +1     
=======================================
+ Hits          506      508    +2     
  Misses          3        3           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@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 effectively resolves an issue where an image preview within a modal was obscured. The fix involves adjusting the z-index and refining the ESC key handling for nested portals, which is a solid approach. The changes are well-supported by a new test case and a demonstration example. My review includes a suggestion to refactor the new demo component to improve code clarity and maintainability by simplifying event handlers and removing duplication.

Comment on lines +8 to +68
const [show, setShow] = useState(false);
return (
<>
<button
onClick={() => {
setShow(true);
}}
>
showModal
</button>
<Dialog
visible={show}
afterOpenChange={open => {
setShow(open);
}}
onClose={() => {
setShow(false);
}}
footer={
<>
<button
onClick={() => {
setShow(false);
}}
>
Cancel
</button>
<button
onClick={() => {
setShow(false);
}}
>
OK
</button>
</>
}
>
<Image
width={200}
alt="svg image"
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
/>
<Image.PreviewGroup
preview={{
onChange: (current, prev) =>
console.log(`current index: ${current}, prev index: ${prev}`),
}}
>
<Image
width={200}
alt="svg image"
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
/>
<Image
width={200}
src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg"
/>
</Image.PreviewGroup>
</Dialog>
</>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The event handlers in this component can be simplified and deduplicated for better readability and maintainability. You can define separate functions for showing and hiding the modal and reuse them. Also, afterOpenChange can directly receive the setShow function.

  const [show, setShow] = useState(false);

  const showModal = () => setShow(true);
  const hideModal = () => setShow(false);

  return (
    <>
      <button
        onClick={showModal}
      >
        showModal
      </button>
      <Dialog
        visible={show}
        afterOpenChange={setShow}
        onClose={hideModal}
        footer={
          <>
            <button
              onClick={hideModal}
            >
              Cancel
            </button>
            <button
              onClick={hideModal}
            >
              OK
            </button>
          </>
        }
      >
        <Image
          width={200}
          alt="svg image"
          src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
        />
        <Image.PreviewGroup
          preview={{
            onChange: (current, prev) =>
              console.log(`current index: ${current}, prev index: ${prev}`),
          }}
        >
          <Image
            width={200}
            alt="svg image"
            src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
          />
          <Image
            width={200}
            src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg"
          />
        </Image.PreviewGroup>
      </Dialog>
    </>
  );

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: 1

🧹 Nitpick comments (3)
docs/examples/nested.tsx (2)

18-25: 审查状态管理逻辑。

Dialog 组件同时使用 afterOpenChangeonClose 来更新 show 状态可能导致状态管理问题:

  • afterOpenChange 是在动画完成后触发的回调,通常不应直接用于控制可见性状态
  • visible 应由父组件完全控制,通过 onClose 更新即可

建议移除 afterOpenChange 中的状态更新,仅保留在 onClose 中:

🔎 建议的重构
      <Dialog
        visible={show}
-       afterOpenChange={open => {
-         setShow(open);
-       }}
        onClose={() => {
          setShow(false);
        }}

52-53: 考虑移除生产环境中的 console.log。

onChange 回调中的 console.log 适合开发和演示,但在生产代码中应该移除或替换为适当的日志机制。

tests/preview.test.tsx (1)

1056-1073: 增强测试覆盖:验证第一次 ESC 后模态框仍然打开。

测试用例正确验证了 ESC 键的分层关闭行为,但可以增加一个断言来明确验证第一次按 ESC 后模态框(Dialog)仍然保持打开状态。

🔎 建议的测试增强
    fireEvent.keyDown(window, { key: 'Escape' });
    expect(baseElement.querySelector('.rc-image-preview')).toBeFalsy();
+   // 验证 Dialog 仍然打开
+   expect(baseElement.querySelector('.rc-dialog')).toBeTruthy();

    fireEvent.keyDown(window, { key: 'Escape' });
    expect(onClose).toHaveBeenCalled();
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3985c4 and 7eccdce.

📒 Files selected for processing (8)
  • .gitignore
  • assets/preview.less
  • docs/demo/nested.md
  • docs/examples/nested.tsx
  • package.json
  • src/Preview/index.tsx
  • tests/preview.test.tsx
  • tsconfig.json
🔇 Additional comments (9)
.gitignore (1)

47-48: LGTM!

添加 .vscode.gitignore 是标准做法,避免将 IDE 特定配置提交到代码库。

docs/demo/nested.md (1)

1-8: LGTM!

标准的 dumi 演示文档格式,正确引用了示例代码。

src/Preview/index.tsx (3)

379-383: LGTM! ESC 处理逻辑正确。

将 ESC 键处理从组件级别的 keydown 事件迁移到 Portal 的 onEsc 回调是此修复的核心。通过检查 top 参数,确保只有当前 Portal 处于顶层时才关闭预览,完美解决了嵌套模态框的 ESC 键冲突问题。


331-343: 确认 ESC 关闭逻辑的移除是有意为之且实现正确。

验证显示:

  1. onKeyDown 处理器正确地仅处理左右箭头键(第337-341行),ESC 逻辑已完全移除
  2. Portal 的 onEsc 处理器已正确实现(第379-382行),在顶部时触发 onClose() 回调
  3. Portal 组件已正确配置 onEsc={onEsc} 属性(第399行)

左右箭头导航功能保持正常,Portal 的 ESC 键处理在所有场景下都能正确触发。


394-400: 确认 autoDestroy={false} 的设计意图,但需要验证 portalRender 状态管理的潜在问题。

Portal 的 autoDestroy={false} 确实是为了支持 CSSMotion 的退出动画:当 open 变为 false 时,Portal 在视觉上关闭,但 DOM 保留以完成 CSSMotion 的 motionLeave 动画。

然而,存在一个状态管理问题:portalRender 状态只在 open=true 时设置为 true,但永远不会被重置为 false。这意味着:

  1. 每次 Preview 打开时,portalRender 已为 true(除首次)
  2. 关闭后,Portal 的 DOM 节点在 getContainer 中持久化,直到 Preview 组件卸载
  3. 若 Preview 组件长期挂载且频繁打开/关闭,Portal DOM 节点会在容器中累积

建议检查:

  • onVisibleChanged 中是否应在动画完成后重置 portalRenderfalse
  • 或在 useLayoutEffect 的清理函数中处理 open=false 的情况
tsconfig.json (1)

14-15: LGTM!

添加测试库的类型定义是标准做法,支持新增的测试文件中使用 Jest 和 Testing Library 的 API。

tests/preview.test.tsx (1)

10-11: LGTM!

新增的导入支持测试用例中使用 useStateDialog 组件,这是测试嵌套场景所必需的。

assets/preview.less (1)

8-8: z-index 值 1055 的选择已确认合理。

搜索结果显示,该 z-index 值高于 Ant Design Modal 的默认值(1000),确保图片预览在嵌套场景中正确堆叠。代码库中无冲突:预览容器主层级为 1055,内部元素(关闭按钮、导航箭头、工具栏)使用相对 z-index: 1,层级结构清晰。此外,组件通过 props 支持 zIndex 自定义,满足不同场景需求。

package.json (1)

44-44: @rc-component/portal v2.1.2 已验证支持 onEsc 回调和 top 参数。

代码在 src/Preview/index.tsx 第 379-383 行直接使用了这些功能:onEsc 回调接收 { top } 参数,并通过检查 top 值来判断是否触发关闭操作。版本升级提供了所需的功能支持。

return (
<Portal open={portalRender} getContainer={getContainer} autoLock={lockScroll}>
<Portal
open={portalRender && open}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

portalRender 我 blame了一下,发现是 #392 中为了解决ssr渲染的问题而引入的,但是由于portal侧会根据open状态去做入栈出栈操作,但是Image这里的portalRender在layoutEffect中置为true之后就恒为true,这就导致了Esc按下后,preview消失,但portal存在,无法出栈,再次按下Esc时Modal也不会关闭。因此我这边加上了open做栈进出,portalRender保留,但只作为ssr gate。应该不会break到原有逻辑
cc @zombieJ

<Portal open={portalRender} getContainer={getContainer} autoLock={lockScroll}>
<Portal
open={portalRender && open}
autoDestroy={false}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

这里是为了确保行为与原逻辑一致

@aojunhao123 aojunhao123 changed the title Fix/image in modal fix: optimize ESC key handling in nested portal scenarios Dec 26, 2025
@zombieJ zombieJ merged commit 0a961d7 into react-component:master Dec 26, 2025
7 of 8 checks passed
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.

V6 Image预览时按Esc键会把Modal一起关闭

2 participants