-
Notifications
You must be signed in to change notification settings - Fork 138
refactor: 重构collapse为details实现 #362
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
base: master
Are you sure you want to change the base?
Changes from 5 commits
3cfdc2e
5ffc99a
df7f09e
ce3ba4c
ff26366
925207f
5026c13
9f5873f
a834f41
3bd0abf
60430c7
4ddcf52
dba7f64
5ed0807
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ coverage | |
es | ||
yarn.lock | ||
package-lock.json | ||
pnpm-lock.yaml | ||
.storybook | ||
.doc | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -35,6 +35,20 @@ | |||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
& > &-item { | ||||||||||||||||||||||||||||||
border-top: @borderStyle; | ||||||||||||||||||||||||||||||
list-style-position: outside; | ||||||||||||||||||||||||||||||
interpolate-size: allow-keywords; | ||||||||||||||||||||||||||||||
overflow: hidden; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
&::details-content { | ||||||||||||||||||||||||||||||
block-size: 0; | ||||||||||||||||||||||||||||||
transition: block-size 0.6s, content-visibility 0.6s; | ||||||||||||||||||||||||||||||
transition-behavior: allow-discrete; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
&[open]::details-content { | ||||||||||||||||||||||||||||||
Comment on lines
+47
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The CSS pseudo-element
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||||||||||||
block-size: auto; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
Comment on lines
+43
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 使用现代CSS特性实现动画过渡 代码引入了一些新的CSS特性来实现基于
这些CSS特性是比较新的,需要注意浏览器兼容性问题。与 建议添加一些注释说明这些属性的浏览器兼容性要求,或考虑为不支持这些特性的浏览器提供兼容性样式。 🤖 Prompt for AI Agents (early access)
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 兼容性如何? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
这只是demo实现,ant实现上考虑下css兼容吧 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 有方案么,最好提前考虑起来。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
你看下是不是可行, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 我尝试引入startTransition,编译报错,需要升级哪里吗? |
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
&:first-child { | ||||||||||||||||||||||||||||||
border-top: none; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,11 +1,11 @@ | ||||||||||||||||||||
import classNames from 'classnames'; | ||||||||||||||||||||
import CSSMotion from 'rc-motion'; | ||||||||||||||||||||
import KeyCode from '@rc-component/util/lib/KeyCode'; | ||||||||||||||||||||
import React from 'react'; | ||||||||||||||||||||
import React, { useMemo } from 'react'; | ||||||||||||||||||||
import type { CollapsePanelProps } from './interface'; | ||||||||||||||||||||
import PanelContent from './PanelContent'; | ||||||||||||||||||||
|
||||||||||||||||||||
const CollapsePanel = React.forwardRef<HTMLDivElement, CollapsePanelProps>((props, ref) => { | ||||||||||||||||||||
const CollapsePanel = React.forwardRef<HTMLDetailsElement, CollapsePanelProps>((props, ref) => { | ||||||||||||||||||||
const { | ||||||||||||||||||||
showArrow = true, | ||||||||||||||||||||
headerClass, | ||||||||||||||||||||
|
@@ -32,9 +32,19 @@ const CollapsePanel = React.forwardRef<HTMLDivElement, CollapsePanelProps>((prop | |||||||||||||||||||
|
||||||||||||||||||||
const ifExtraExist = extra !== null && extra !== undefined && typeof extra !== 'boolean'; | ||||||||||||||||||||
|
||||||||||||||||||||
// ? 用于判断浏览器是否支持::details-content 否则使用CSSMotion | ||||||||||||||||||||
const supportsDetailsContentSelector = useMemo( | ||||||||||||||||||||
() => | ||||||||||||||||||||
typeof document !== 'undefined' && typeof document.createElement === 'function' | ||||||||||||||||||||
? CSS.supports('selector(details::details-content)') | ||||||||||||||||||||
: false, | ||||||||||||||||||||
[], | ||||||||||||||||||||
); | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain浏览器特性检测实现 使用 不过,可能需要考虑在组件初始挂载后处理 SSR 水合差异(hydration mismatch)的问题。 需要验证此实现在 SSR 环境中是否会导致客户端与服务端渲染结果不一致。在服务端 🏁 Script executed: #!/bin/bash
# 检查项目是否使用了支持 SSR 的框架和相关配置
grep -r "SSR\|server-side rendering\|hydration" --include="*.md" --include="*.js" --include="*.ts" --include="*.tsx" .
# 检查是否有处理 hydration mismatch 的代码
grep -r "suppressHydrationWarning\|useEffect.*hydration\|supportsDetailsContentSelector" --include="*.tsx" --include="*.ts" . Length of output: 106462 注意 SSR 与水合不一致 目前 建议:
请根据项目的 SSR 配置和业务需求,调整组件实现,确保服务端与客户端渲染的一致性。 🤖 Prompt for AI Agents
|
||||||||||||||||||||
|
||||||||||||||||||||
const collapsibleProps = { | ||||||||||||||||||||
onClick: () => { | ||||||||||||||||||||
onClick: (e: React.MouseEvent) => { | ||||||||||||||||||||
onItemClick?.(panelKey); | ||||||||||||||||||||
e.stopPropagation(); | ||||||||||||||||||||
}, | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 阻止 点击位于 - onClick: (e: React.MouseEvent) => {
- onItemClick?.(panelKey);
- e.stopPropagation();
- },
+ onClick: (e: React.MouseEvent) => {
+ e.preventDefault(); // 阻止原生 toggle
+ e.stopPropagation(); // 阻止冒泡
+ onItemClick?.(panelKey);
+ }, 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 看看这个? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @afc163 CodeRabbit 提出的建议是正确的!这个问题很重要,因为在使用原生 问题分析:
修复建议: onClick: (e: React.MouseEvent) => {
e.preventDefault(); // 阻止原生 toggle 行为
e.stopPropagation(); // 阻止事件冒泡
onItemClick?.(panelKey); // React 控制状态更新
}, 这个修复确保了:
这个修改对于从 div 实现迁移到原生 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
已处理 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||
onKeyDown: (e: React.KeyboardEvent) => { | ||||||||||||||||||||
if (e.key === 'Enter' || e.keyCode === KeyCode.ENTER || e.which === KeyCode.ENTER) { | ||||||||||||||||||||
|
@@ -67,6 +77,8 @@ const CollapsePanel = React.forwardRef<HTMLDivElement, CollapsePanelProps>((prop | |||||||||||||||||||
[`${prefixCls}-item-disabled`]: disabled, | ||||||||||||||||||||
}, | ||||||||||||||||||||
className, | ||||||||||||||||||||
// ? 修改为details实现后动画是作用在details元素上 需要将motionName设置在details上 | ||||||||||||||||||||
supportsDetailsContentSelector && openMotion?.motionName, | ||||||||||||||||||||
); | ||||||||||||||||||||
|
||||||||||||||||||||
const headerClassName = classNames( | ||||||||||||||||||||
|
@@ -79,16 +91,78 @@ const CollapsePanel = React.forwardRef<HTMLDivElement, CollapsePanelProps>((prop | |||||||||||||||||||
); | ||||||||||||||||||||
|
||||||||||||||||||||
// ======================== HeaderProps ======================== | ||||||||||||||||||||
const headerProps: React.HTMLAttributes<HTMLDivElement> = { | ||||||||||||||||||||
const headerProps: React.HTMLAttributes<HTMLElement> = { | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nitpick] Since
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||
className: headerClassName, | ||||||||||||||||||||
style: styles?.header, | ||||||||||||||||||||
...(['header', 'icon'].includes(collapsible) ? {} : collapsibleProps), | ||||||||||||||||||||
}; | ||||||||||||||||||||
|
||||||||||||||||||||
// ======================== Render ======================== | ||||||||||||||||||||
|
||||||||||||||||||||
const leavedClassName = `${prefixCls}-panel-hidden`; | ||||||||||||||||||||
const createPanelContent = ( | ||||||||||||||||||||
props: Partial<{ | ||||||||||||||||||||
className: string; | ||||||||||||||||||||
style: React.CSSProperties; | ||||||||||||||||||||
motionRef: (node: HTMLDivElement) => void; | ||||||||||||||||||||
}>, | ||||||||||||||||||||
) => { | ||||||||||||||||||||
const { className, style, motionRef } = props; | ||||||||||||||||||||
|
||||||||||||||||||||
return ( | ||||||||||||||||||||
<PanelContent | ||||||||||||||||||||
ref={motionRef} | ||||||||||||||||||||
prefixCls={prefixCls} | ||||||||||||||||||||
className={className} | ||||||||||||||||||||
classNames={customizeClassNames} | ||||||||||||||||||||
style={style} | ||||||||||||||||||||
styles={styles} | ||||||||||||||||||||
isActive={isActive} | ||||||||||||||||||||
forceRender={forceRender} | ||||||||||||||||||||
role={accordion ? 'tabpanel' : void 0} | ||||||||||||||||||||
> | ||||||||||||||||||||
{children} | ||||||||||||||||||||
</PanelContent> | ||||||||||||||||||||
); | ||||||||||||||||||||
}; | ||||||||||||||||||||
let detailsChildren = ( | ||||||||||||||||||||
<CSSMotion | ||||||||||||||||||||
visible={isActive} | ||||||||||||||||||||
leavedClassName={leavedClassName} | ||||||||||||||||||||
{...openMotion} | ||||||||||||||||||||
forceRender={forceRender} | ||||||||||||||||||||
removeOnLeave={destroyInactivePanel} | ||||||||||||||||||||
> | ||||||||||||||||||||
{({ className, style }, motionRef) => | ||||||||||||||||||||
createPanelContent({ | ||||||||||||||||||||
className, | ||||||||||||||||||||
style, | ||||||||||||||||||||
motionRef, | ||||||||||||||||||||
}) | ||||||||||||||||||||
} | ||||||||||||||||||||
</CSSMotion> | ||||||||||||||||||||
); | ||||||||||||||||||||
|
||||||||||||||||||||
// ? 模拟CSSMotion子元素生命周期管理 | ||||||||||||||||||||
if (supportsDetailsContentSelector) { | ||||||||||||||||||||
if (isActive) { | ||||||||||||||||||||
detailsChildren = createPanelContent({}); | ||||||||||||||||||||
} else if (!destroyInactivePanel && leavedClassName) { | ||||||||||||||||||||
detailsChildren = createPanelContent({ | ||||||||||||||||||||
className: leavedClassName, | ||||||||||||||||||||
}); | ||||||||||||||||||||
} else if (forceRender || (!destroyInactivePanel && !leavedClassName)) { | ||||||||||||||||||||
detailsChildren = createPanelContent({ | ||||||||||||||||||||
style: { display: 'none' }, | ||||||||||||||||||||
}); | ||||||||||||||||||||
} else { | ||||||||||||||||||||
detailsChildren = null; | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
return ( | ||||||||||||||||||||
<div {...resetProps} ref={ref} className={collapsePanelClassNames}> | ||||||||||||||||||||
<div {...headerProps}> | ||||||||||||||||||||
<details {...resetProps} ref={ref} className={collapsePanelClassNames} open={isActive}> | ||||||||||||||||||||
<summary {...headerProps}> | ||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
{showArrow && iconNode} | ||||||||||||||||||||
<span | ||||||||||||||||||||
className={classNames(`${prefixCls}-title`, customizeClassNames?.title)} | ||||||||||||||||||||
|
@@ -98,33 +172,9 @@ const CollapsePanel = React.forwardRef<HTMLDivElement, CollapsePanelProps>((prop | |||||||||||||||||||
{header} | ||||||||||||||||||||
</span> | ||||||||||||||||||||
{ifExtraExist && <div className={`${prefixCls}-extra`}>{extra}</div>} | ||||||||||||||||||||
</div> | ||||||||||||||||||||
<CSSMotion | ||||||||||||||||||||
visible={isActive} | ||||||||||||||||||||
leavedClassName={`${prefixCls}-panel-hidden`} | ||||||||||||||||||||
{...openMotion} | ||||||||||||||||||||
forceRender={forceRender} | ||||||||||||||||||||
removeOnLeave={destroyInactivePanel} | ||||||||||||||||||||
> | ||||||||||||||||||||
{({ className: motionClassName, style: motionStyle }, motionRef) => { | ||||||||||||||||||||
return ( | ||||||||||||||||||||
<PanelContent | ||||||||||||||||||||
ref={motionRef} | ||||||||||||||||||||
prefixCls={prefixCls} | ||||||||||||||||||||
className={motionClassName} | ||||||||||||||||||||
classNames={customizeClassNames} | ||||||||||||||||||||
style={motionStyle} | ||||||||||||||||||||
styles={styles} | ||||||||||||||||||||
isActive={isActive} | ||||||||||||||||||||
forceRender={forceRender} | ||||||||||||||||||||
role={accordion ? 'tabpanel' : void 0} | ||||||||||||||||||||
> | ||||||||||||||||||||
{children} | ||||||||||||||||||||
</PanelContent> | ||||||||||||||||||||
); | ||||||||||||||||||||
}} | ||||||||||||||||||||
</CSSMotion> | ||||||||||||||||||||
</div> | ||||||||||||||||||||
</summary> | ||||||||||||||||||||
{detailsChildren} | ||||||||||||||||||||
</details> | ||||||||||||||||||||
); | ||||||||||||||||||||
}); | ||||||||||||||||||||
|
||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://developer.mozilla.org/en-US/docs/Web/CSS/interpolate-size#browser_compatibility
浏览器兼容性有点差,这个会没办法在 antd 中使用。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
有什么好建议吗