Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions src/hooks/useItems.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import toArray from '@rc-component/util/lib/Children/toArray';
import React from 'react';
import type { CollapsePanelProps, CollapseProps, ItemType } from '../interface';
import type { CollapsePanelProps, CollapseProps, ItemType, SemanticName } from '../interface';
import CollapsePanel from '../Panel';
import clsx from 'clsx';

type Props = Pick<
CollapsePanelProps,
Expand All @@ -22,7 +23,7 @@ const convertItemsToNodes = (items: ItemType[], props: Props) => {
openMotion,
expandIcon,
classNames: collapseClassNames,
styles,
styles: collapseStyles,

Choose a reason for hiding this comment

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

high

Renaming styles to collapseStyles is a good step to avoid naming collisions. However, this fix for panel-specific styles and classNames appears to be incomplete because it only applies to panels defined via the items prop.

The getNewChild function (which handles Panel components passed as children, starting on line 88) has not been updated. It still passes the Collapse component's styles and classNames to each CollapsePanel, ignoring the props on the Panel itself.

To ensure consistent behavior, I recommend applying a similar fix to the getNewChild function. This would involve:

  1. Destructuring classNames and styles from child.props.
  2. Passing childClassNames || collapseClassNames and childStyles || collapseStyles to the cloned element.

This will make the fix comprehensive, even though getNewChild is deprecated.

} = props;

return items.map((item, index) => {
Expand All @@ -33,6 +34,8 @@ const convertItemsToNodes = (items: ItemType[], props: Props) => {
collapsible: rawCollapsible,
onItemClick: rawOnItemClick,
destroyOnHidden: rawDestroyOnHidden,
classNames,
styles,
...restProps
} = item;

Expand All @@ -57,11 +60,29 @@ const convertItemsToNodes = (items: ItemType[], props: Props) => {
isActive = activeKey.indexOf(key) > -1;
}

const mergeClassNames: Partial<Record<SemanticName, string>> = {
Copy link
Member

Choose a reason for hiding this comment

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

我建议是在 Collapse 顶层做一个 SemanticContext,然后传入 classNames 和 styles,然后在 panel 里读取 className 和 style 以及 contextClassName 和 contextStyle,在消费的时候做聚合。
现在合并在 useItems 这层,如果未来额外加 semantic structure 很容易忘记。

...collapseClassNames,
header: clsx(collapseClassNames?.header, classNames?.header),
body: clsx(collapseClassNames?.body, classNames?.body),
};

const mergeStyles: Partial<Record<SemanticName, React.CSSProperties>> = {
...collapseStyles,
header: {
...collapseStyles?.header,
...styles?.header,
},
body: {
...collapseStyles?.body,
...styles?.body,
},
};
Comment on lines +63 to +79
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

合并逻辑缺少 titleicon 的处理。

SemanticName 类型包含 'header' | 'title' | 'body' | 'icon' 四个值,但当前合并逻辑仅显式处理了 headerbody。如果 panel 级别设置了 classNames.titleclassNames.iconstyles.titlestyles.icon,这些值不会与 collapse 级别的值合并。

建议补全 titleicon 的合并逻辑:

 const mergeClassNames: Partial<Record<SemanticName, string>> = {
   ...collapseClassNames,
   header: clsx(collapseClassNames?.header, classNames?.header),
   body: clsx(collapseClassNames?.body, classNames?.body),
+  title: clsx(collapseClassNames?.title, classNames?.title),
+  icon: clsx(collapseClassNames?.icon, classNames?.icon),
 };

 const mergeStyles: Partial<Record<SemanticName, React.CSSProperties>> = {
   ...collapseStyles,
   header: {
     ...collapseStyles?.header,
     ...styles?.header,
   },
   body: {
     ...collapseStyles?.body,
     ...styles?.body,
   },
+  title: {
+    ...collapseStyles?.title,
+    ...styles?.title,
+  },
+  icon: {
+    ...collapseStyles?.icon,
+    ...styles?.icon,
+  },
 };
📝 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 mergeClassNames: Partial<Record<SemanticName, string>> = {
...collapseClassNames,
header: clsx(collapseClassNames?.header, classNames?.header),
body: clsx(collapseClassNames?.body, classNames?.body),
};
const mergeStyles: Partial<Record<SemanticName, React.CSSProperties>> = {
...collapseStyles,
header: {
...collapseStyles?.header,
...styles?.header,
},
body: {
...collapseStyles?.body,
...styles?.body,
},
};
const mergeClassNames: Partial<Record<SemanticName, string>> = {
...collapseClassNames,
header: clsx(collapseClassNames?.header, classNames?.header),
body: clsx(collapseClassNames?.body, classNames?.body),
title: clsx(collapseClassNames?.title, classNames?.title),
icon: clsx(collapseClassNames?.icon, classNames?.icon),
};
const mergeStyles: Partial<Record<SemanticName, React.CSSProperties>> = {
...collapseStyles,
header: {
...collapseStyles?.header,
...styles?.header,
},
body: {
...collapseStyles?.body,
...styles?.body,
},
title: {
...collapseStyles?.title,
...styles?.title,
},
icon: {
...collapseStyles?.icon,
...styles?.icon,
},
};
🤖 Prompt for AI Agents
In src/hooks/useItems.tsx around lines 63 to 79, the merge logic only handles
'header' and 'body' but misses 'title' and 'icon', so panel-level
classNames.title/icon and styles.title/icon are not merged with collapse-level
values; update mergeClassNames to include title: clsx(collapseClassNames?.title,
classNames?.title) and icon: clsx(collapseClassNames?.icon, classNames?.icon),
and update mergeStyles to include title: { ...collapseStyles?.title,
...styles?.title } and icon: { ...collapseStyles?.icon, ...styles?.icon } so all
four SemanticName entries are consistently merged.


return (
<CollapsePanel
{...restProps}
classNames={collapseClassNames}
styles={styles}
classNames={mergeClassNames}
styles={mergeStyles}
prefixCls={prefixCls}
key={key}
panelKey={key}
Expand Down
54 changes: 54 additions & 0 deletions tests/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -898,5 +898,59 @@ describe('collapse', () => {
expect(titleElement.style.color).toBe('green');
expect(iconElement.style.color).toBe('yellow');
});

it('should support styles and classNames in panel', () => {
const customStyles = {
header: { color: 'red' },
body: { color: 'blue' },
title: { color: 'green' },
icon: { color: 'yellow' },
};
const customClassnames = {
header: 'custom-header',
body: 'custom-body',
};

const { container } = render(
<Collapse
activeKey={['1']}
styles={customStyles}
classNames={customClassnames}
items={[
{
key: '1',
styles: {
header: {
color: 'blue',
fontSize: 20,
},
body: {
fontSize: 20,
},
},
classNames: {
header: 'custom-header-panel',
body: 'custom-body-panel',
},
label: 'title',
},
]}
/>,
);
const headerElement = container.querySelector('.rc-collapse-header') as HTMLElement;
const bodyElement = container.querySelector('.rc-collapse-body') as HTMLElement;

// check classNames
expect(headerElement.classList).toContain('custom-header');
expect(headerElement.classList).toContain('custom-header-panel');
expect(bodyElement.classList).toContain('custom-body');
expect(bodyElement.classList).toContain('custom-body-panel');

// check styles
expect(headerElement.style.color).toBe('blue');
Copy link
Member

@zombieJ zombieJ Nov 26, 2025

Choose a reason for hiding this comment

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

可以直接 expect(xx).toHaveStyle({ xxx: xxx })

expect(headerElement.style.fontSize).toBe('20px');
expect(bodyElement.style.color).toBe('blue');
expect(bodyElement.style.fontSize).toBe('20px');
});
});
});