Skip to content

Commit 0bcf9be

Browse files
author
Illia Obukhau
committed
feat(accordion-web): add "Load content" group property
1 parent acd6135 commit 0bcf9be

File tree

9 files changed

+110
-7
lines changed

9 files changed

+110
-7
lines changed

packages/pluggableWidgets/accordion-web/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- We added new group property ("Load content") which controls how widgets should be rendered. Use this prop if you have problems with page load time.
12+
913
## [2.1.2] - 2022-07-14
1014

1115
### Fixed

packages/pluggableWidgets/accordion-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "accordion-web",
33
"widgetName": "Accordion",
4-
"version": "2.1.2",
4+
"version": "2.2.0",
55
"description": "A Mendix pluggable widget to display expandable list items.",
66
"copyright": "© Mendix Technology BV 2022. All rights reserved.",
77
"license": "Apache-2.0",

packages/pluggableWidgets/accordion-web/src/Accordion.editorPreview.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export function preview(props: AccordionPreviewProps): ReactElement {
5858
const expandIcon = mapPreviewIconToWebIcon(props.expandIcon);
5959
const collapseIcon = mapPreviewIconToWebIcon(props.collapseIcon);
6060

61+
// eslint-disable-next-line react-hooks/rules-of-hooks
6162
const generateIcon = useIconGenerator(
6263
props.animateIcon,
6364
{

packages/pluggableWidgets/accordion-web/src/Accordion.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ function translateGroups(groups: AccordionContainerProps["groups"]): AccordionGr
6262
: group.initialCollapsedState === "collapsed",
6363
visible: group.visible.value!,
6464
dynamicClassName: group.dynamicClass?.value,
65-
onToggleCompletion: group.collapsed?.setValue
65+
onToggleCompletion: group.collapsed?.setValue,
66+
loadContent: group.loadContent
6667
};
6768
});
6869
}

packages/pluggableWidgets/accordion-web/src/Accordion.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="utf-8" ?>
2-
<widget id="com.mendix.widget.web.accordion.Accordion" needsEntityContext="true" offlineCapable="true" pluginWidget="true" xmlns="http://www.mendix.com/widget/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../../../../node_modules/mendix/custom_widget.xsd">
2+
<widget id="com.mendix.widget.web.accordion.Accordion" needsEntityContext="true" offlineCapable="true" pluginWidget="true" xmlns="http://www.mendix.com/widget/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../node_modules/mendix/custom_widget.xsd">
33
<name>Accordion</name>
44
<description>Toggle the display of sections of content.</description>
55
<studioProCategory>Structure</studioProCategory>
@@ -63,6 +63,14 @@
6363
<description />
6464
<returnType type="String" />
6565
</property>
66+
<property key="loadContent" type="enumeration" defaultValue="always">
67+
<caption>Load content</caption>
68+
<description>This property determines when the widgets should be rendered and data is fetched. The “Always” option will always load the widgets regardless whether the group is expanded. The “When expanded” option can reduce the initial (page) load time, but will increase the load time when expanding the group.</description>
69+
<enumerationValues>
70+
<enumerationValue key="always">Always</enumerationValue>
71+
<enumerationValue key="whenExpanded">When expanded</enumerationValue>
72+
</enumerationValues>
73+
</property>
6674
</propertyGroup>
6775
<propertyGroup caption="State">
6876
<property key="initialCollapsedState" type="enumeration" defaultValue="collapsed">

packages/pluggableWidgets/accordion-web/src/components/Accordion.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import { AccordionContainerProps } from "../../typings/AccordionProps";
1515
import classNames from "classnames";
1616

1717
export type AccordionGroups = Array<
18-
Pick<AccordionGroupProps, "header" | "content" | "visible" | "dynamicClassName" | "onToggleCompletion"> & {
18+
Pick<
19+
AccordionGroupProps,
20+
"header" | "content" | "visible" | "dynamicClassName" | "onToggleCompletion" | "loadContent"
21+
> & {
1922
collapsed?: boolean;
2023
initiallyCollapsed?: boolean;
2124
}
@@ -165,6 +168,7 @@ function AccordionGroupWrapper(props: AccordionGroupWrapperProps): ReactElement
165168
animateContent={props.animateContent}
166169
generateHeaderIcon={props.generateHeaderIcon}
167170
showHeaderIcon={props.showHeaderIcon}
171+
loadContent={props.loadContent}
168172
/>
169173
);
170174
}

packages/pluggableWidgets/accordion-web/src/components/AccordionGroup.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import classNames from "classnames";
33
import { MountOnceReady } from "./MountOnceReady";
44
import "../ui/accordion-main.scss";
55

6+
/* eslint-disable no-unused-vars */
67
export const enum Target {
78
FIRST = "first",
89
LAST = "last",
910
PREVIOUS = "previous",
1011
NEXT = "next"
1112
}
13+
/* eslint-enable no-unused-vars */
1214

1315
export type AccordionGroupIcon = { icon: ReactNode } | { expandIcon: ReactNode; collapseIcon: ReactNode };
1416

@@ -26,10 +28,18 @@ export interface AccordionGroupProps {
2628
animateContent?: boolean;
2729
generateHeaderIcon?: (collapsed: boolean) => ReactElement;
2830
showHeaderIcon?: "right" | "left" | "no";
31+
loadContent?: "always" | "whenExpanded";
2932
}
3033

3134
export function AccordionGroup(props: AccordionGroupProps): ReactElement | null {
32-
const { animateContent, changeFocus, showHeaderIcon, toggleCollapsed, onToggleCompletion } = props;
35+
const {
36+
animateContent,
37+
changeFocus,
38+
showHeaderIcon,
39+
toggleCollapsed,
40+
onToggleCompletion,
41+
loadContent = "always"
42+
} = props;
3343

3444
const [renderCollapsed, setRenderCollapsed] = useState(props.collapsed);
3545
const previousRenderCollapsed = useRef(renderCollapsed);
@@ -38,6 +48,7 @@ export function AccordionGroup(props: AccordionGroupProps): ReactElement | null
3848
const rootRef = useRef<HTMLDivElement>(null);
3949
const contentWrapperRef = useRef<HTMLDivElement>(null);
4050
const contentRef = useRef<HTMLDivElement>(null);
51+
const lazyRender = loadContent === "whenExpanded";
4152

4253
const completeTransitioning = useCallback((): void => {
4354
if (contentWrapperRef.current && rootRef.current && animatingContent.current) {
@@ -178,7 +189,11 @@ export function AccordionGroup(props: AccordionGroupProps): ReactElement | null
178189
aria-labelledby={`${props.id}HeaderButton`}
179190
>
180191
<div ref={contentRef} className={"widget-accordion-group-content"}>
181-
<MountOnceReady ready={!renderCollapsed}>{props.content}</MountOnceReady>
192+
{lazyRender ? (
193+
<MountOnceReady ready={!renderCollapsed}>{props.content}</MountOnceReady>
194+
) : (
195+
props.content
196+
)}
182197
</div>
183198
</div>
184199
</section>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import "@testing-library/jest-dom";
2+
import { createElement } from "react";
3+
import { render, screen } from "@testing-library/react";
4+
import { MountOnceReady } from "../MountOnceReady";
5+
6+
describe("MountOnceReady", () => {
7+
it("do not mount children if ready prop is always 'false'", () => {
8+
const max = 10;
9+
const min = 3;
10+
const rerenderCount = Math.floor(Math.random() * (max - min)) + min;
11+
const text = "Should not be mounted";
12+
const child = (): JSX.Element => <div>{text}</div>;
13+
14+
const { rerender } = render(<MountOnceReady ready={false}>{child()}</MountOnceReady>);
15+
expect(screen.queryByText(text)).toBeNull();
16+
17+
for (let n = 0; n < rerenderCount; n += 1) {
18+
rerender(<MountOnceReady ready={false}>{child()}</MountOnceReady>);
19+
expect(screen.queryByText(text)).toBeNull();
20+
}
21+
});
22+
23+
it("mount content once ready prop became 'true'", async () => {
24+
const text = "Should be mounted";
25+
const child = (): JSX.Element => <div>{text}</div>;
26+
27+
const { rerender } = render(<MountOnceReady ready={false}>{child()}</MountOnceReady>);
28+
expect(screen.queryByText(text)).toBeNull();
29+
30+
rerender(<MountOnceReady ready={false}>{child()}</MountOnceReady>);
31+
expect(screen.queryByText(text)).toBeNull();
32+
33+
rerender(<MountOnceReady ready>{child()}</MountOnceReady>);
34+
expect(await screen.findByText(text)).toBeVisible();
35+
});
36+
37+
it("stay mounted if ready change from 'true' to 'false'", async () => {
38+
const text = "Should be mounted";
39+
const child = (): JSX.Element => <div>{text}</div>;
40+
41+
const { rerender } = render(<MountOnceReady ready={false}>{child()}</MountOnceReady>);
42+
expect(screen.queryByText(text)).toBeNull();
43+
44+
rerender(<MountOnceReady ready>{child()}</MountOnceReady>);
45+
expect(await screen.findByText(text)).toBeVisible();
46+
47+
rerender(<MountOnceReady ready={false}>{child()}</MountOnceReady>);
48+
expect(await screen.findByText(text)).toBeVisible();
49+
50+
rerender(<MountOnceReady ready>{child()}</MountOnceReady>);
51+
expect(await screen.findByText(text)).toBeVisible();
52+
53+
rerender(<MountOnceReady ready={false}>{child()}</MountOnceReady>);
54+
expect(await screen.findByText(text)).toBeVisible();
55+
});
56+
57+
it("mount right away if ready is 'true' on initial render", async () => {
58+
const text = "Should be right away";
59+
const child = (): JSX.Element => <div>{text}</div>;
60+
61+
const { rerender } = render(<MountOnceReady ready>{child()}</MountOnceReady>);
62+
expect(await screen.findByText(text)).toBeVisible();
63+
64+
rerender(<MountOnceReady ready={false}>{child()}</MountOnceReady>);
65+
expect(await screen.findByText(text)).toBeVisible();
66+
67+
rerender(<MountOnceReady ready>{child()}</MountOnceReady>);
68+
expect(await screen.findByText(text)).toBeVisible();
69+
});
70+
});

packages/pluggableWidgets/accordion-web/src/package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<package xmlns="http://www.mendix.com/package/1.0/">
3-
<clientModule name="Accordion" version="2.1.2" xmlns="http://www.mendix.com/clientModule/1.0/">
3+
<clientModule name="Accordion" version="2.2.0" xmlns="http://www.mendix.com/clientModule/1.0/">
44
<widgetFiles>
55
<widgetFile path="Accordion.xml" />
66
</widgetFiles>

0 commit comments

Comments
 (0)