Skip to content

Commit 46758a3

Browse files
al3xbzhgcornut
authored andcommitted
feat(expansion-panel): add closeMode so children are unmounted/hidden
1 parent 9066b5f commit 46758a3

File tree

4 files changed

+50
-12
lines changed

4 files changed

+50
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- `ExpansionPanel`: added `closeMode` to `"unmount"` or `"hide"` children when the item is closed.
13+
1014
## [3.17.2][] - 2025-10-01
1115

1216
### Fixed

packages/lumx-react/src/components/expansion-panel/ExpansionPanel.stories.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ export default {
1818
component: ExpansionPanel,
1919
args: {
2020
'toggleButtonProps.label': 'Toggle',
21-
children: 'Content',
21+
children: (
22+
<Text as="p" typography="body1" color="dark-L2" className="lumx-spacing-padding-big">
23+
content
24+
</Text>
25+
),
2226
label: 'Label',
2327
},
2428
decorators: [withNestedProps()],
@@ -77,3 +81,11 @@ export const Nested = {
7781
);
7882
},
7983
};
84+
85+
/** Hide component instead of unmounting it */
86+
export const HideChildren = {
87+
args: {
88+
hasBackground: true,
89+
closeMode: 'hide',
90+
},
91+
};

packages/lumx-react/src/components/expansion-panel/ExpansionPanel.test.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,11 @@ describe(`<${ExpansionPanel.displayName}>`, () => {
134134
expect(onToggleOpen).toHaveBeenCalledWith(false, expect.anything());
135135
});
136136

137-
it('should hide children after toggling the expansion panel', async () => {
137+
it('should unmount children after toggling the expansion panel', async () => {
138138
const user = userEvent.setup();
139139
const { query } = setup({}, { controlled: true });
140140

141-
// Content is not visible by default
141+
// Content is not mounted by default
142142
expect(query.content()).not.toBeInTheDocument();
143143

144144
await user.click(query.header() as any);
@@ -149,6 +149,25 @@ describe(`<${ExpansionPanel.displayName}>`, () => {
149149

150150
expect(query.content()).not.toBeInTheDocument();
151151
});
152+
153+
it('should hide children after toggling the expansion panel', async () => {
154+
const user = userEvent.setup();
155+
const { element, query } = setup({ closeMode: 'hide' }, { controlled: true });
156+
157+
// Content is hidden (but mounted) by default
158+
expect(query.content()).toBeInTheDocument();
159+
expect(element).toHaveClass(`${CLASSNAME}--is-close`);
160+
161+
await user.click(query.header() as any);
162+
163+
expect(query.content()).toBeInTheDocument();
164+
expect(element).toHaveClass(`${CLASSNAME}--is-open`);
165+
166+
await user.click(query.header() as any);
167+
168+
expect(query.content()).toBeInTheDocument();
169+
expect(element).toHaveClass(`${CLASSNAME}--is-close`);
170+
});
152171
});
153172

154173
// Common tests suite.

packages/lumx-react/src/components/expansion-panel/ExpansionPanel.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { mdiChevronDown, mdiChevronUp } from '@lumx/icons';
77
import isEmpty from 'lodash/isEmpty';
88

99
import { ColorPalette, DragHandle, Emphasis, IconButton, IconButtonProps, Theme } from '@lumx/react';
10-
import { GenericProps, HasTheme, isComponent } from '@lumx/react/utils/type';
10+
import { GenericProps, HasCloseMode, HasTheme, isComponent } from '@lumx/react/utils/type';
1111
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
1212
import { partitionMulti } from '@lumx/react/utils/partitionMulti';
1313
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
@@ -17,7 +17,7 @@ import { IS_BROWSER } from '@lumx/react/constants';
1717
/**
1818
* Defines the props of the component.
1919
*/
20-
export interface ExpansionPanelProps extends GenericProps, HasTheme {
20+
export interface ExpansionPanelProps extends GenericProps, HasCloseMode, HasTheme {
2121
/** Whether the expansion panel has a background. */
2222
hasBackground?: boolean;
2323
/** Whether the header has a divider. */
@@ -52,7 +52,9 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
5252
/**
5353
* Component default props.
5454
*/
55-
const DEFAULT_PROPS: Partial<ExpansionPanelProps> = {};
55+
const DEFAULT_PROPS: Partial<ExpansionPanelProps> = {
56+
closeMode: 'unmount',
57+
};
5658

5759
const isDragHandle = isComponent(DragHandle);
5860
const isHeader = isComponent('header');
@@ -69,6 +71,7 @@ export const ExpansionPanel = forwardRef<ExpansionPanelProps, HTMLDivElement>((p
6971
const defaultTheme = useTheme() || Theme.light;
7072
const {
7173
className,
74+
closeMode = DEFAULT_PROPS.closeMode,
7275
children: anyChildren,
7376
hasBackground,
7477
hasHeaderDivider,
@@ -127,32 +130,32 @@ export const ExpansionPanel = forwardRef<ExpansionPanelProps, HTMLDivElement>((p
127130

128131
const wrapperRef = useRef<HTMLDivElement>(null);
129132

130-
// Children visible while the open/close transition is running
133+
// Children stay visible while the open/close transition is running
131134
const [isChildrenVisible, setChildrenVisible] = React.useState(isOpen);
132135

133136
const isOpenRef = React.useRef(isOpen);
134137
React.useEffect(() => {
135-
if (isOpen) {
138+
if (isOpen || closeMode === 'hide') {
136139
setChildrenVisible(true);
137140
} else if (!IS_BROWSER) {
138141
// Outside a browser we can't wait for the transition
139142
setChildrenVisible(false);
140143
}
141144
isOpenRef.current = isOpen;
142-
}, [isOpen]);
145+
}, [closeMode, isOpen]);
143146

144-
// Change children visibility on transition end
147+
// Change children's visibility on the transition end
145148
React.useEffect(() => {
146149
const { current: wrapper } = wrapperRef;
147150
if (!IS_BROWSER || !wrapper) {
148151
return undefined;
149152
}
150153
const onTransitionEnd = () => {
151-
setChildrenVisible(isOpenRef.current);
154+
setChildrenVisible(isOpenRef.current || closeMode === 'hide');
152155
};
153156
wrapper.addEventListener('transitionend', onTransitionEnd);
154157
return () => wrapper.removeEventListener('transitionend', onTransitionEnd);
155-
}, []);
158+
}, [closeMode]);
156159

157160
return (
158161
<section ref={ref} {...forwardedProps} className={rootClassName}>

0 commit comments

Comments
 (0)