Skip to content

Commit e295ef8

Browse files
authored
Merge pull request #140 from bcdev/clarasb-134-add_accordion_component
Add accordion component
2 parents 1a060e6 + 975bfc2 commit e295ef8

File tree

9 files changed

+232
-0
lines changed

9 files changed

+232
-0
lines changed

chartlets.js/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
* Added icon support for `Button`, `IconButton` and `Tabs` components.
1010
(#124).
11+
12+
* Added (MUI) component `Accordion`. (#41, #134)
1113

1214
* Fixed handling of `style` prop in `Tabs` component and added prop
1315
`iconPosition`. (#135, #136)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2019-2026 by Brockmann Consult Development team
3+
* Permissions are hereby granted under the terms of the MIT License:
4+
* https://opensource.org/licenses/MIT.
5+
*/
6+
7+
import { render, screen, fireEvent } from "@testing-library/react";
8+
import { describe, expect, it } from "vitest";
9+
10+
import { Accordion } from "./Accordion";
11+
import { createChangeHandler } from "@/plugins/mui/common.test";
12+
13+
describe("Accordion", () => {
14+
it("should render the Accordion component", () => {
15+
render(
16+
<Accordion
17+
id="acc"
18+
type="Accordion"
19+
label="My Accordion"
20+
onChange={() => {}}
21+
/>,
22+
);
23+
24+
expect(screen.getByText("My Accordion")).not.toBeUndefined();
25+
});
26+
27+
it("should fire 'expanded' property", () => {
28+
const { recordedEvents, onChange } = createChangeHandler();
29+
30+
render(
31+
<Accordion
32+
id="acc"
33+
type="Accordion"
34+
expanded={false}
35+
label="My Accordion"
36+
onChange={onChange}
37+
></Accordion>,
38+
);
39+
40+
// MUI Summary renders a button element
41+
fireEvent.click(screen.getByRole("button"));
42+
43+
expect(recordedEvents.length).toEqual(1);
44+
expect(recordedEvents[0]).toEqual({
45+
componentType: "Accordion",
46+
id: "acc",
47+
property: "expanded",
48+
value: true,
49+
});
50+
});
51+
});
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (c) 2019-2026 by Brockmann Consult Development team
3+
* Permissions are hereby granted under the terms of the MIT License:
4+
* https://opensource.org/licenses/MIT.
5+
*/
6+
7+
import MuiAccordion from "@mui/material/Accordion";
8+
import MuiAccordionDetails from "@mui/material/AccordionDetails";
9+
import MuiAccordionSummary from "@mui/material/AccordionSummary";
10+
import MuiTypography from "@mui/material/Typography";
11+
12+
import type { ComponentState, ComponentProps } from "@/index";
13+
import { Children } from "@/index";
14+
import { Icon } from "./Icon";
15+
import type { SyntheticEvent } from "react";
16+
17+
interface AccordionState extends ComponentState {
18+
label?: string;
19+
icon?: string;
20+
expanded?: boolean;
21+
disabled?: boolean;
22+
}
23+
24+
interface AccordionProps extends ComponentProps, AccordionState {}
25+
26+
export const Accordion = ({
27+
id,
28+
style,
29+
label,
30+
icon,
31+
expanded,
32+
disabled,
33+
children: nodes,
34+
onChange,
35+
}: AccordionProps) => {
36+
const handleChange = (_event: SyntheticEvent, isExpanded: boolean) => {
37+
if (id) {
38+
onChange?.({
39+
componentType: "Accordion",
40+
id,
41+
property: "expanded",
42+
value: isExpanded,
43+
});
44+
}
45+
};
46+
return (
47+
<div>
48+
<MuiAccordion
49+
id={id}
50+
style={style}
51+
expanded={expanded}
52+
disabled={disabled}
53+
onChange={handleChange}
54+
>
55+
<MuiAccordionSummary
56+
expandIcon={icon ? <Icon iconName={icon} /> : undefined}
57+
>
58+
{label ? (
59+
<MuiTypography component="span">{label}</MuiTypography>
60+
) : null}
61+
</MuiAccordionSummary>
62+
63+
{nodes && (
64+
<MuiAccordionDetails>
65+
<Children nodes={nodes} onChange={onChange} />
66+
</MuiAccordionDetails>
67+
)}
68+
</MuiAccordion>
69+
</div>
70+
);
71+
};

chartlets.js/packages/lib/src/plugins/mui/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import type { Plugin } from "@/index";
8+
import { Accordion } from "./Accordion";
89
import { Box } from "./Box";
910
import { Button } from "./Button";
1011
import { Checkbox } from "./Checkbox";
@@ -25,6 +26,7 @@ import { Table } from "@/plugins/mui/Table";
2526
export default function mui(): Plugin {
2627
return {
2728
components: [
29+
["Accordion", Accordion],
2830
["Box", Box],
2931
["Button", Button],
3032
["Checkbox", Checkbox],

chartlets.py/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
* Added `size` and removed `variant` property from `IconButton`
44
component to align with component in chartlets.js. (#124)
5+
6+
* Added (MUI) component `Accordion`. (#41, #134)
57

68
* Added `iconPosition` to `Tabs` Component. (#135, #136)
79

chartlets.py/chartlets/components/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Permissions are hereby granted under the terms of the MIT License:
33
# https://opensource.org/licenses/MIT.
44

5+
from .accordion import Accordion
56
from .box import Box
67
from .button import Button
78
from .button import IconButton
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright (c) 2019-2026 by Brockmann Consult Development team
2+
# Permissions are hereby granted under the terms of the MIT License:
3+
# https://opensource.org/licenses/MIT.
4+
5+
6+
from dataclasses import dataclass, field
7+
8+
from chartlets import Component
9+
10+
@dataclass(frozen=True)
11+
class Accordion(Component):
12+
"""Accordion container."""
13+
14+
label: str | None = None
15+
"""Header of the accordion."""
16+
17+
icon: str | None = None
18+
"""Material icon name for the expand icon (e.g. 'expand_more')."""
19+
20+
expanded: bool = field(default=False)
21+
"""If set, controls whether the accordion is expanded."""
22+
23+
disabled: bool | None = None
24+
"""If set, controls whether the accordion is disabled."""
25+
26+
children: list[Component] = field(default_factory=list)
27+
"""Accordion content."""

chartlets.py/demo/my_extension/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .my_panel_6 import panel as my_panel_6
1212
from .my_panel_7 import panel as my_panel_7
1313
from .my_panel_8 import panel as my_panel_8
14+
from .my_panel_9 import panel as my_panel_9
1415

1516

1617
ext = Extension(__name__)
@@ -22,3 +23,4 @@
2223
ext.add(my_panel_6)
2324
ext.add(my_panel_7)
2425
ext.add(my_panel_8)
26+
ext.add(my_panel_9)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Copyright (c) 2019-2026 by Brockmann Consult Development team
2+
# Permissions are hereby granted under the terms of the MIT License:
3+
# https://opensource.org/licenses/MIT.
4+
5+
6+
from chartlets import Component, State
7+
from chartlets.components import (
8+
Accordion,
9+
Typography,
10+
Box,
11+
Table
12+
)
13+
from chartlets.components.table import TableColumn, TableRow
14+
15+
16+
from server.context import Context
17+
from server.panel import Panel
18+
19+
20+
panel = Panel(__name__, title="Panel I")
21+
22+
23+
# noinspection PyUnusedLocal
24+
@panel.layout()
25+
def render_panel(
26+
ctx: Context,
27+
) -> Component:
28+
columns: list[TableColumn] = [
29+
{"id": "id", "label": "ID", "sortDirection": "desc"},
30+
{
31+
"id": "firstName",
32+
"label": "First Name",
33+
"align": "left",
34+
"sortDirection": "desc",
35+
},
36+
{"id": "lastName", "label": "Last Name", "align": "center"},
37+
{"id": "age", "label": "Age"},
38+
]
39+
40+
rows: TableRow = [
41+
["1", "John", "Doe", 30],
42+
["2", "Jane", "Smith", 25],
43+
["3", "Peter", "Jones", 40],
44+
]
45+
46+
table = Table(id="table", rows=rows, columns=columns, hover=True)
47+
48+
info_text = Typography(id="info_text", children=["This is a text."], style={"margin": "30px"})
49+
50+
accordion1 = Accordion(
51+
id="accordion1",
52+
label="Accordion No.1",
53+
icon="arrow_drop_down",
54+
children=[info_text],
55+
)
56+
57+
accordion2 = Accordion(
58+
id="accordion2",
59+
label="Accordion No.2",
60+
icon="arrow_drop_down",
61+
# expanded=True,
62+
# disabled=True
63+
children=[table, info_text],
64+
)
65+
66+
return Box(
67+
style={
68+
"display": "flex",
69+
"flexDirection": "column",
70+
"width": "100%",
71+
"height": "100%",
72+
},
73+
children=[accordion1, accordion2],
74+
)

0 commit comments

Comments
 (0)