Skip to content
5 changes: 5 additions & 0 deletions .changeset/fix-card-horizontal-double-click.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clickhouse/click-ui": patch
---

Fix `CardHorizontal` double `onClick` handler bug where clicking the inner Button would invoke `onButtonClick` and window.open(infoUrl) twice due to event bubbling. Added e.stopPropagation() to the button's click handler.
28 changes: 28 additions & 0 deletions src/components/CardHorizontal/CardHorizontal.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CardHorizontal, CardHorizontalProps } from '@/components/CardHorizontal';
import { renderCUI } from '@/utils/test-utils';

Expand Down Expand Up @@ -187,4 +188,31 @@ describe('CardHorizontal Component', () => {
expect(windowOpenSpy).not.toHaveBeenCalled();
windowOpenSpy.mockRestore();
});

it('should call onButtonClick only once when inner button is clicked', async () => {
const onButtonClick = vitest.fn();
const { getByRole } = renderCard({
title: 'Test Card',
infoText: 'Click me',
onButtonClick,
});

await userEvent.click(getByRole('button'));

expect(onButtonClick).toHaveBeenCalledTimes(1);
});

it('should open infoUrl only once when inner button is clicked', async () => {
const windowOpenSpy = vitest.spyOn(window, 'open').mockImplementation(() => null);
const { getByRole } = renderCard({
title: 'Test Card',
infoText: 'Click me',
infoUrl: 'https://example.com',
});

await userEvent.click(getByRole('button'));

expect(windowOpenSpy).toHaveBeenCalledTimes(1);
windowOpenSpy.mockRestore();
});
});
38 changes: 25 additions & 13 deletions src/components/CardHorizontal/CardHorizontal.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useCallback, type MouseEvent } from 'react';
import { styled } from 'styled-components';
import { Badge } from '@/components/Badge';
import { Button } from '@/components/Button';
Expand Down Expand Up @@ -195,19 +196,30 @@ export const CardHorizontal = ({
onButtonClick,
...props
}: CardHorizontalProps) => {
const handleClick = (e: React.MouseEvent<HTMLElement>) => {
if (disabled) {
e.preventDefault();
return;
}
const handleClick = useCallback(
(e: MouseEvent<HTMLElement>) => {
if (disabled) {
e.preventDefault();
return;
}

if (typeof onButtonClick === 'function') {
onButtonClick(e);
}
if (infoUrl && infoUrl.length > 0) {
window.open(infoUrl, '_blank');
}
};
if (typeof onButtonClick === 'function') {
onButtonClick(e);
}
if (infoUrl && infoUrl.length > 0) {
window.open(infoUrl, '_blank');
}
},
[disabled, onButtonClick, infoUrl]
);

const handleButtonClick = useCallback(
(e: MouseEvent<HTMLElement>) => {
e.stopPropagation();
handleClick(e);
},
[handleClick]
);
return (
<Wrapper
$disabled={disabled}
Expand Down Expand Up @@ -280,7 +292,7 @@ export const CardHorizontal = ({
>
<Button
label={infoText}
onClick={handleClick}
onClick={handleButtonClick}
disabled={disabled}
fillWidth
/>
Expand Down
Loading