From ca9bc918f25989e263eec5871531e3768a0d00fa Mon Sep 17 00:00:00 2001 From: Anton Chio Date: Sat, 4 Apr 2026 15:05:23 +0800 Subject: [PATCH 1/4] refactor: convert EuiAccordion to function component with useState and useEuiTheme --- .../src/components/accordion/accordion.tsx | 226 ++++++++---------- 1 file changed, 96 insertions(+), 130 deletions(-) diff --git a/packages/eui/src/components/accordion/accordion.tsx b/packages/eui/src/components/accordion/accordion.tsx index 7964ba60ce0b..4a755708b270 100644 --- a/packages/eui/src/components/accordion/accordion.tsx +++ b/packages/eui/src/components/accordion/accordion.tsx @@ -6,14 +6,15 @@ * Side Public License, v 1. */ -import React, { Component, HTMLAttributes, ReactNode } from 'react'; +import React, { + FunctionComponent, + HTMLAttributes, + ReactNode, + useState, +} from 'react'; import classNames from 'classnames'; -import { - htmlIdGenerator, - withEuiTheme, - WithEuiThemeProps, -} from '../../services'; +import { useEuiTheme, useGeneratedHtmlId } from '../../services'; import { CommonProps } from '../common'; import { EuiLoadingSpinner } from '../loading'; import type { EuiButtonIconProps } from '../button'; @@ -114,133 +115,98 @@ export type EuiAccordionProps = CommonProps & isDisabled?: boolean; }; -type EuiAccordionState = { - isOpen: boolean; -}; - -export class EuiAccordionClass extends Component< - WithEuiThemeProps & EuiAccordionProps, - EuiAccordionState -> { - static defaultProps = { - initialIsOpen: false, - borders: 'none' as const, - paddingSize: 'none' as const, - arrowDisplay: 'left' as const, - isLoading: false, - isDisabled: false, - isLoadingMessage: false, - element: 'div' as const, - buttonElement: 'button' as const, - role: 'group' as const, - }; - - state = { - isOpen: this.props.forceState - ? this.props.forceState === 'open' - : this.props.initialIsOpen!, - }; - - get isOpen() { - return this.props.forceState - ? this.props.forceState === 'open' - : this.state.isOpen; - } - - onToggle = () => { - const { forceState } = this.props; +export const EuiAccordionClass: FunctionComponent = ({ + children, + className, + id, + role = 'group', + element: Element = 'div', + buttonElement = 'button', + buttonProps, + buttonClassName, + buttonContentClassName, + buttonContent, + arrowDisplay = 'left', + arrowProps, + extraAction, + paddingSize = 'none', + borders = 'none', + initialIsOpen = false, + forceState, + isLoading = false, + isLoadingMessage = false, + isDisabled = false, + onToggle, + ...rest +}) => { + const [isOpenState, setIsOpenState] = useState( + forceState ? forceState === 'open' : initialIsOpen + ); + + const isOpen = forceState ? forceState === 'open' : isOpenState; + + const onAccordionToggle = () => { if (forceState) { - const nextState = !this.isOpen; - this.props.onToggle?.(nextState); + onToggle?.(!isOpen); } else { - this.setState( - (prevState) => ({ - isOpen: !prevState.isOpen, - }), - () => { - this.props.onToggle?.(this.state.isOpen); - } - ); + setIsOpenState((prevIsOpen) => { + const nextState = !prevIsOpen; + onToggle?.(nextState); + return nextState; + }); } }; - generatedId = htmlIdGenerator()(); - - render() { - const { - children, - className, - id, - role, - element: Element = 'div', - buttonElement, - buttonProps, - buttonClassName, - buttonContentClassName, - buttonContent, - arrowDisplay, - arrowProps, - extraAction, - paddingSize, - borders, - initialIsOpen, - forceState, - isLoading, - isLoadingMessage, - isDisabled, - theme, - ...rest - } = this.props; - - const classes = classNames( - 'euiAccordion', - { 'euiAccordion-isOpen': this.isOpen }, - className - ); - - const styles = euiAccordionStyles(theme); - const cssStyles = [ - styles.euiAccordion, - borders !== 'none' && styles.borders.borders, - borders !== 'none' && styles.borders[borders!], - ]; - - const buttonId = buttonProps?.id ?? this.generatedId; - - return ( - - : extraAction} - /> - - - {children} - - - ); - } -} + const generatedId = useGeneratedHtmlId(); + const buttonId = buttonProps?.id ?? generatedId; + + const euiTheme = useEuiTheme(); + const classes = classNames( + 'euiAccordion', + { 'euiAccordion-isOpen': isOpen }, + className + ); + + const styles = euiAccordionStyles(euiTheme); + const cssStyles = [ + styles.euiAccordion, + borders !== 'none' && styles.borders.borders, + borders !== 'none' && styles.borders[borders], + ]; + + return ( + + : extraAction} + /> + + + {children} + + + ); +}; -export const EuiAccordion = withEuiTheme(EuiAccordionClass); +export const EuiAccordion = EuiAccordionClass; From 5797296c97c10e6a97d7ebce76e4dfa68026a560 Mon Sep 17 00:00:00 2001 From: Anton Chio Date: Sat, 4 Apr 2026 15:53:27 +0800 Subject: [PATCH 2/4] chore: add changelog for #9558 --- packages/eui/changelogs/upcoming/9558.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 packages/eui/changelogs/upcoming/9558.md diff --git a/packages/eui/changelogs/upcoming/9558.md b/packages/eui/changelogs/upcoming/9558.md new file mode 100644 index 000000000000..a89751d26bae --- /dev/null +++ b/packages/eui/changelogs/upcoming/9558.md @@ -0,0 +1,2 @@ +- Added/Updated ... + From b9212377cf889d95ab7054a29f8c31ccd1edd29a Mon Sep 17 00:00:00 2001 From: Anton Chio Date: Tue, 5 May 2026 14:30:46 +0800 Subject: [PATCH 3/4] refactor: update EuiAccordion to use useEuiMemoizedStyles and streamline toggle logic --- .../eui/src/components/accordion/accordion.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/eui/src/components/accordion/accordion.tsx b/packages/eui/src/components/accordion/accordion.tsx index 4a755708b270..cac3c79a56db 100644 --- a/packages/eui/src/components/accordion/accordion.tsx +++ b/packages/eui/src/components/accordion/accordion.tsx @@ -14,7 +14,7 @@ import React, { } from 'react'; import classNames from 'classnames'; -import { useEuiTheme, useGeneratedHtmlId } from '../../services'; +import { useEuiMemoizedStyles, useGeneratedHtmlId } from '../../services'; import { CommonProps } from '../common'; import { EuiLoadingSpinner } from '../loading'; import type { EuiButtonIconProps } from '../button'; @@ -115,7 +115,7 @@ export type EuiAccordionProps = CommonProps & isDisabled?: boolean; }; -export const EuiAccordionClass: FunctionComponent = ({ +export const EuiAccordion: FunctionComponent = ({ children, className, id, @@ -149,25 +149,22 @@ export const EuiAccordionClass: FunctionComponent = ({ if (forceState) { onToggle?.(!isOpen); } else { - setIsOpenState((prevIsOpen) => { - const nextState = !prevIsOpen; - onToggle?.(nextState); - return nextState; - }); + const nextState = !isOpenState; + setIsOpenState(nextState); + onToggle?.(nextState); } }; const generatedId = useGeneratedHtmlId(); const buttonId = buttonProps?.id ?? generatedId; - const euiTheme = useEuiTheme(); const classes = classNames( 'euiAccordion', { 'euiAccordion-isOpen': isOpen }, className ); - const styles = euiAccordionStyles(euiTheme); + const styles = useEuiMemoizedStyles(euiAccordionStyles); const cssStyles = [ styles.euiAccordion, borders !== 'none' && styles.borders.borders, @@ -208,5 +205,3 @@ export const EuiAccordionClass: FunctionComponent = ({ ); }; - -export const EuiAccordion = EuiAccordionClass; From bbad3a9e96dbd9b6fe4afe36c84f9e760a60ac4a Mon Sep 17 00:00:00 2001 From: Anton Chio Date: Tue, 5 May 2026 14:33:40 +0800 Subject: [PATCH 4/4] chore: remove changelog for #9558 --- packages/eui/changelogs/upcoming/9558.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 packages/eui/changelogs/upcoming/9558.md diff --git a/packages/eui/changelogs/upcoming/9558.md b/packages/eui/changelogs/upcoming/9558.md deleted file mode 100644 index a89751d26bae..000000000000 --- a/packages/eui/changelogs/upcoming/9558.md +++ /dev/null @@ -1,2 +0,0 @@ -- Added/Updated ... -