Skip to content
Merged
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
33 changes: 30 additions & 3 deletions src/hooks/useItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import toArray from '@rc-component/util/lib/Children/toArray';
import React from 'react';
import type { CollapsePanelProps, CollapseProps, ItemType } from '../interface';
import CollapsePanel from '../Panel';
import clsx from 'clsx';

type Props = Pick<
CollapsePanelProps,
Expand All @@ -11,6 +12,30 @@ type Props = Pick<
activeKey: React.Key[];
};

function mergeSemantic<T>(src: T, tgt: T, mergeFn: (a: any, b: any) => any) {
if (!src || !tgt) {
return src || tgt;
}

const keys = Array.from(new Set([...Object.keys(src), ...Object.keys(tgt)]));
const result = {};
keys.forEach((key) => {
result[key] = mergeFn(src[key], tgt[key]);
});
return result;
}

function mergeSemanticClassNames<T>(src: T, tgt: T) {
return mergeSemantic(src, tgt, (a: string, b: string) => clsx(a, b));
}

function mergeSemanticStyles<T>(src: T, tgt: T) {
return mergeSemantic(src, tgt, (a: React.CSSProperties, b: React.CSSProperties) => ({
...a,
...b,
}));
}

const convertItemsToNodes = (items: ItemType[], props: Props) => {
const {
prefixCls,
Expand All @@ -22,7 +47,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 +58,8 @@ const convertItemsToNodes = (items: ItemType[], props: Props) => {
collapsible: rawCollapsible,
onItemClick: rawOnItemClick,
destroyOnHidden: rawDestroyOnHidden,
classNames,
styles,
...restProps
} = item;

Expand Down Expand Up @@ -60,8 +87,8 @@ const convertItemsToNodes = (items: ItemType[], props: Props) => {
return (
<CollapsePanel
{...restProps}
classNames={collapseClassNames}
styles={styles}
classNames={mergeSemanticClassNames(collapseClassNames, classNames)}
styles={mergeSemanticStyles(collapseStyles, styles)}
prefixCls={prefixCls}
key={key}
panelKey={key}
Expand Down
62 changes: 62 additions & 0 deletions tests/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -898,5 +898,67 @@ 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,
},
title: {
color: 'red',
},
icon: {
color: 'blue',
},
},
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;
const titleElement = container.querySelector('.rc-collapse-title') as HTMLElement;
const iconElement = container.querySelector('.rc-collapse-expand-icon') 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).toHaveStyle({ color: 'blue', fontSize: '20px' });
expect(bodyElement).toHaveStyle({ color: 'blue', fontSize: '20px' });
expect(titleElement).toHaveStyle({ color: 'red' });
expect(iconElement).toHaveStyle({ color: 'blue' });
});
});
});