Skip to content

Commit 3ac6454

Browse files
authored
feat(ExpandableSection): functional toggleContent (#12063)
* feat(ExpandableSection): allow function toggleContent Signed-off-by: gitdallas <[email protected]> * add example Signed-off-by: gitdallas <[email protected]> --------- Signed-off-by: gitdallas <[email protected]>
1 parent c7b90a1 commit 3ac6454

File tree

4 files changed

+63
-3
lines changed

4 files changed

+63
-3
lines changed

packages/react-core/src/components/ExpandableSection/ExpandableSection.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ export interface ExpandableSectionProps extends Omit<React.HTMLProps<HTMLDivElem
4444
* use the onToggle property of the expandable section toggle sub-component.
4545
*/
4646
onToggle?: (event: React.MouseEvent, isExpanded: boolean) => void;
47-
/** React node that appears in the attached toggle in place of the toggleText property. */
48-
toggleContent?: React.ReactNode;
47+
/** React node that appears in the attached toggle in place of the toggleText property.
48+
* Can also be a function that receives the expanded state and returns a React node.
49+
*/
50+
toggleContent?: React.ReactNode | ((isExpanded: boolean) => React.ReactNode);
4951
/** Text that appears in the attached toggle. */
5052
toggleText?: string;
5153
/** Text that appears in the attached toggle when collapsed (will override toggleText if
@@ -246,6 +248,9 @@ class ExpandableSection extends Component<ExpandableSectionProps, ExpandableSect
246248
propOrStateIsExpanded
247249
);
248250

251+
const computedToggleContent =
252+
typeof toggleContent === 'function' ? toggleContent(propOrStateIsExpanded) : toggleContent;
253+
249254
const expandableToggle = !isDetached && (
250255
<div className={`${styles.expandableSection}__toggle`}>
251256
<Button
@@ -265,7 +270,7 @@ class ExpandableSection extends Component<ExpandableSectionProps, ExpandableSect
265270
aria-label={toggleAriaLabel}
266271
aria-labelledby={toggleAriaLabelledBy}
267272
>
268-
{toggleContent || computedToggleText}
273+
{computedToggleContent || computedToggleText}
269274
</Button>
270275
</div>
271276
);

packages/react-core/src/components/ExpandableSection/__tests__/ExpandableSection.test.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,37 @@ test('Renders with aria-labelledby when toggleAriaLabelledBy is passed', () => {
208208

209209
expect(screen.getByRole('button')).toHaveAccessibleName('Test label');
210210
});
211+
test('Renders toggleContent as a function in uncontrolled mode (collapsed)', () => {
212+
render(
213+
<ExpandableSection toggleContent={(isExpanded) => (isExpanded ? 'Hide details' : 'Show details')}>
214+
Test content
215+
</ExpandableSection>
216+
);
217+
218+
expect(screen.getByRole('button', { name: 'Show details' })).toBeInTheDocument();
219+
});
220+
221+
test('Renders toggleContent as a function in uncontrolled mode (expanded after click)', async () => {
222+
const user = userEvent.setup();
223+
224+
render(
225+
<ExpandableSection toggleContent={(isExpanded) => (isExpanded ? 'Hide details' : 'Show details')}>
226+
Test content
227+
</ExpandableSection>
228+
);
229+
230+
const button = screen.getByRole('button', { name: 'Show details' });
231+
await user.click(button);
232+
233+
expect(screen.getByRole('button', { name: 'Hide details' })).toBeInTheDocument();
234+
});
235+
236+
test('Renders toggleContent as a function in controlled mode', () => {
237+
render(
238+
<ExpandableSection isExpanded={true} toggleContent={(isExpanded) => (isExpanded ? 'Collapse' : 'Expand')}>
239+
Test content
240+
</ExpandableSection>
241+
);
242+
243+
expect(screen.getByRole('button', { name: 'Collapse' })).toBeInTheDocument();
244+
});

packages/react-core/src/components/ExpandableSection/examples/ExpandableSection.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle
2828

2929
```
3030

31+
### Uncontrolled with dynamic toggle content (function)
32+
33+
Use `toggleContent` as a function to dynamically render different content based on the expanded state without managing state yourself.
34+
35+
```ts file="ExpandableSectionUncontrolledDynamicToggleFunction.tsx"
36+
37+
```
38+
3139
### Detached
3240

3341
When passing the `isDetached` property into `<ExpandableSection>`, you must also manually pass in the same `toggleId` and `contentId` properties to both `<ExpandableSection>` and `<ExpandableSectionToggle>`. This will link the content to the toggle via ARIA attributes.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ExpandableSection } from '@patternfly/react-core';
2+
3+
export const ExpandableSectionUncontrolledDynamicToggleFunction: React.FunctionComponent = () => (
4+
<ExpandableSection
5+
toggleContent={(isExpanded) =>
6+
isExpanded
7+
? 'Show less uncontrolled dynamic toggle example content'
8+
: 'Show more uncontrolled dynamic toggle example content'
9+
}
10+
>
11+
This content is visible only when the component is expanded.
12+
</ExpandableSection>
13+
);

0 commit comments

Comments
 (0)