diff --git a/packages/eui/changelogs/upcoming/8786.md b/packages/eui/changelogs/upcoming/8786.md new file mode 100644 index 00000000000..4429eaed6f7 --- /dev/null +++ b/packages/eui/changelogs/upcoming/8786.md @@ -0,0 +1,4 @@ +**Bug fixes** + +- Fixed `onChange` being triggered twice when the checkbox in `EuiCheckableCard` is clicked + diff --git a/packages/eui/src/components/card/checkable_card/checkable_card.spec.tsx b/packages/eui/src/components/card/checkable_card/checkable_card.spec.tsx new file mode 100644 index 00000000000..81d2b27de55 --- /dev/null +++ b/packages/eui/src/components/card/checkable_card/checkable_card.spec.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/// +/// +/// + +import React, { FunctionComponent, useState } from 'react'; + +import { EuiCheckableCard, type EuiCheckableCardProps } from '../index'; + +describe('EuiCheckableCard', () => { + const StatefulCheckableCard: FunctionComponent< + Partial + > = ({ checkableType = 'checkbox', ...rest }) => { + const [isChecked, setChecked] = useState(false); + + return ( + setChecked((checked) => !checked)} + /> + ); + }; + + describe('Click behavior', () => { + it('fired onChange only once when the checkbox is clicked', () => { + cy.realMount(); + + cy.get('[data-test-subj=checkableCard]').realClick(); + + cy.get('[data-test-subj=checkableCard]').should('be.checked'); + }); + }); +}); diff --git a/packages/eui/src/components/card/checkable_card/checkable_card.tsx b/packages/eui/src/components/card/checkable_card/checkable_card.tsx index ccaa25be195..da3e8e77800 100644 --- a/packages/eui/src/components/card/checkable_card/checkable_card.tsx +++ b/packages/eui/src/components/card/checkable_card/checkable_card.tsx @@ -75,6 +75,7 @@ export const EuiCheckableCard: FunctionComponent = ({ const { id } = rest; const labelEl = useRef(null); + const inputEl = useRef(null); const classes = classNames('euiCheckableCard', className); let checkableElement; @@ -88,14 +89,19 @@ export const EuiCheckableCard: FunctionComponent = ({ ); } else { checkableElement = ( - + ); } const labelClasses = classNames('euiCheckableCard__label'); - const onChangeAffordance = () => { - if (labelEl.current) { + const onChangeAffordance = (e: React.MouseEvent) => { + if (labelEl.current && e.target !== inputEl.current) { labelEl.current.click(); } }; diff --git a/packages/eui/src/components/form/checkbox/checkbox.tsx b/packages/eui/src/components/form/checkbox/checkbox.tsx index 280cb5ee144..45d286c14dd 100644 --- a/packages/eui/src/components/form/checkbox/checkbox.tsx +++ b/packages/eui/src/components/form/checkbox/checkbox.tsx @@ -13,6 +13,7 @@ import React, { InputHTMLAttributes, LabelHTMLAttributes, useCallback, + Ref, } from 'react'; import classNames from 'classnames'; @@ -28,7 +29,7 @@ export interface EuiCheckboxProps id: string; checked?: boolean; onChange: ChangeEventHandler; // overriding to make it required - inputRef?: (element: HTMLInputElement) => void; + inputRef?: Ref | ((element: HTMLInputElement) => void); label?: ReactNode; disabled?: boolean; indeterminate?: boolean; diff --git a/packages/website/docs/components/containers/card.mdx b/packages/website/docs/components/containers/card.mdx index d74d9e213c2..8553f6c4605 100644 --- a/packages/website/docs/components/containers/card.mdx +++ b/packages/website/docs/components/containers/card.mdx @@ -363,7 +363,7 @@ export default () => { checkableType="checkbox" value="checkbox1" checked={checkbox} - onChange={() => setCheckbox(!checkbox)} + onChange={() => setCheckbox(checked => !checked)} /> ); };