diff --git a/chartlets.js/CHANGES.md b/chartlets.js/CHANGES.md index cab1a85..9f7ae02 100644 --- a/chartlets.js/CHANGES.md +++ b/chartlets.js/CHANGES.md @@ -8,6 +8,8 @@ * Added icon support for `Button`, `IconButton` and `Tabs` components. (#124). + +* Added (MUI) component `Accordion`. (#134) ## Version 0.1.7 (from 2025/12/03) diff --git a/chartlets.js/packages/lib/src/plugins/mui/Accordion.test.tsx b/chartlets.js/packages/lib/src/plugins/mui/Accordion.test.tsx new file mode 100644 index 0000000..4472d76 --- /dev/null +++ b/chartlets.js/packages/lib/src/plugins/mui/Accordion.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2026 by Brockmann Consult Development team + * Permissions are hereby granted under the terms of the MIT License: + * https://opensource.org/licenses/MIT. + */ + +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { Accordion } from "./Accordion"; +import { createChangeHandler } from "@/plugins/mui/common.test"; + +describe("Accordion", () => { + it("should render the Accordion component", () => { + render( + {}} + />, + ); + + expect(screen.getByText("My Accordion")).not.toBeUndefined(); + }); + + it("should fire 'expanded' property", () => { + const { recordedEvents, onChange } = createChangeHandler(); + + render( + , + ); + + // MUI Summary renders a button element + fireEvent.click(screen.getByRole("button")); + + expect(recordedEvents.length).toEqual(1); + expect(recordedEvents[0]).toEqual({ + componentType: "Accordion", + id: "acc", + property: "expanded", + value: true, + }); + }); +}); diff --git a/chartlets.js/packages/lib/src/plugins/mui/Accordion.tsx b/chartlets.js/packages/lib/src/plugins/mui/Accordion.tsx new file mode 100644 index 0000000..486ebbb --- /dev/null +++ b/chartlets.js/packages/lib/src/plugins/mui/Accordion.tsx @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019-2026 by Brockmann Consult Development team + * Permissions are hereby granted under the terms of the MIT License: + * https://opensource.org/licenses/MIT. + */ + +import MuiAccordion from "@mui/material/Accordion"; +import MuiAccordionDetails from "@mui/material/AccordionDetails"; +import MuiAccordionSummary from "@mui/material/AccordionSummary"; +import MuiTypography from "@mui/material/Typography"; + +import type { ComponentState, ComponentProps } from "@/index"; +import { Children } from "@/index"; +import { Icon } from "./Icon"; +import type { SyntheticEvent } from "react"; + +interface AccordionState extends ComponentState { + label?: string; + icon?: string; + expanded?: boolean; + disabled?: boolean; +} + +interface AccordionProps extends ComponentProps, AccordionState {} + +export const Accordion = ({ + id, + style, + label, + icon, + expanded, + disabled, + children: nodes, + onChange, +}: AccordionProps) => { + const handleChange = (_event: SyntheticEvent, isExpanded: boolean) => { + if (id) { + onChange?.({ + componentType: "Accordion", + id, + property: "expanded", + value: isExpanded, + }); + } + }; + return ( +
+ + : undefined} + > + {label ? ( + {label} + ) : null} + + + {nodes && ( + + + + )} + +
+ ); +}; diff --git a/chartlets.js/packages/lib/src/plugins/mui/index.ts b/chartlets.js/packages/lib/src/plugins/mui/index.ts index 04b3267..649d15e 100644 --- a/chartlets.js/packages/lib/src/plugins/mui/index.ts +++ b/chartlets.js/packages/lib/src/plugins/mui/index.ts @@ -5,6 +5,7 @@ */ import type { Plugin } from "@/index"; +import { Accordion } from "./Accordion"; import { Box } from "./Box"; import { Button } from "./Button"; import { Checkbox } from "./Checkbox"; @@ -25,6 +26,7 @@ import { Table } from "@/plugins/mui/Table"; export default function mui(): Plugin { return { components: [ + ["Accordion", Accordion], ["Box", Box], ["Button", Button], ["Checkbox", Checkbox], diff --git a/chartlets.py/CHANGES.md b/chartlets.py/CHANGES.md index f318520..0ce0f42 100644 --- a/chartlets.py/CHANGES.md +++ b/chartlets.py/CHANGES.md @@ -2,6 +2,8 @@ * Added `size` and removed `variant` property from `IconButton` component to align with component in chartlets.js. (#124) + +* Added (MUI) component `Accordion`. (#134) ## Version 0.1.7 (from 2025/12/03) diff --git a/chartlets.py/chartlets/components/__init__.py b/chartlets.py/chartlets/components/__init__.py index fc037cf..9689def 100644 --- a/chartlets.py/chartlets/components/__init__.py +++ b/chartlets.py/chartlets/components/__init__.py @@ -2,6 +2,7 @@ # Permissions are hereby granted under the terms of the MIT License: # https://opensource.org/licenses/MIT. +from .accordion import Accordion from .box import Box from .button import Button from .button import IconButton diff --git a/chartlets.py/chartlets/components/accordion.py b/chartlets.py/chartlets/components/accordion.py new file mode 100644 index 0000000..9e765e9 --- /dev/null +++ b/chartlets.py/chartlets/components/accordion.py @@ -0,0 +1,27 @@ +# Copyright (c) 2019-2026 by Brockmann Consult Development team +# Permissions are hereby granted under the terms of the MIT License: +# https://opensource.org/licenses/MIT. + + +from dataclasses import dataclass, field + +from chartlets import Component + +@dataclass(frozen=True) +class Accordion(Component): + """Accordion container.""" + + label: str | None = None + """Header of the accordion.""" + + icon: str | None = None + """Material icon name for the expand icon (e.g. 'expand_more').""" + + expanded: bool = field(default=False) + """If set, controls whether the accordion is expanded.""" + + disabled: bool | None = None + """If set, controls whether the accordion is disabled.""" + + children: list[Component] = field(default_factory=list) + """Accordion content.""" diff --git a/chartlets.py/demo/my_extension/__init__.py b/chartlets.py/demo/my_extension/__init__.py index 776367d..bd22edc 100644 --- a/chartlets.py/demo/my_extension/__init__.py +++ b/chartlets.py/demo/my_extension/__init__.py @@ -11,6 +11,7 @@ from .my_panel_6 import panel as my_panel_6 from .my_panel_7 import panel as my_panel_7 from .my_panel_8 import panel as my_panel_8 +from .my_panel_9 import panel as my_panel_9 ext = Extension(__name__) @@ -22,3 +23,4 @@ ext.add(my_panel_6) ext.add(my_panel_7) ext.add(my_panel_8) +ext.add(my_panel_9) diff --git a/chartlets.py/demo/my_extension/my_panel_9.py b/chartlets.py/demo/my_extension/my_panel_9.py new file mode 100644 index 0000000..3c158af --- /dev/null +++ b/chartlets.py/demo/my_extension/my_panel_9.py @@ -0,0 +1,74 @@ +# Copyright (c) 2019-2026 by Brockmann Consult Development team +# Permissions are hereby granted under the terms of the MIT License: +# https://opensource.org/licenses/MIT. + + +from chartlets import Component, State +from chartlets.components import ( + Accordion, + Typography, + Box, + Table +) +from chartlets.components.table import TableColumn, TableRow + + +from server.context import Context +from server.panel import Panel + + +panel = Panel(__name__, title="Panel I") + + +# noinspection PyUnusedLocal +@panel.layout() +def render_panel( + ctx: Context, +) -> Component: + columns: list[TableColumn] = [ + {"id": "id", "label": "ID", "sortDirection": "desc"}, + { + "id": "firstName", + "label": "First Name", + "align": "left", + "sortDirection": "desc", + }, + {"id": "lastName", "label": "Last Name", "align": "center"}, + {"id": "age", "label": "Age"}, + ] + + rows: TableRow = [ + ["1", "John", "Doe", 30], + ["2", "Jane", "Smith", 25], + ["3", "Peter", "Jones", 40], + ] + + table = Table(id="table", rows=rows, columns=columns, hover=True) + + info_text = Typography(id="info_text", children=["This is a text."]) + + accordion1 = Accordion( + id="accordion1", + label="Accordion No.1", + icon="arrow_drop_down", + children=[info_text], + ) + + accordion2 = Accordion( + id="accordion2", + label="Accordion No.2", + icon="arrow_drop_down", + # expanded=True, + # disabled=True + children=[table], + ) + + return Box( + style={ + "display": "flex", + "flexDirection": "column", + "width": "100%", + "height": "100%", + }, + children=[accordion1, accordion2], + )