Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions chartlets.js/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

* New (MUI) components
- `LinearProgress`
- `RadioGroup` and `Radio`
- `Switch`

* Supporting `tooltip` property for interactive MUI components.
Expand Down
62 changes: 62 additions & 0 deletions chartlets.js/packages/lib/src/plugins/mui/RadioGroup.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { describe, it, expect } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { createChangeHandler } from "./common.test";
import { RadioGroup } from "./RadioGroup";

describe("RadioGroup", () => {
it("should render the component", () => {
render(
<RadioGroup
id="rg"
type={"RadioGroup"}
label={"Gender"}
value={"f"}
children={[
{ type: "Radio", value: "f", label: "Female" },
{ type: "Radio", value: "m", label: "Male" },
{ type: "Radio", value: "v", label: "Varying" },
]}
onChange={() => {}}
row
dense
/>,
);
// to inspect rendered component, do:
// expect(document.querySelector("#rg")).toEqual({});
expect(screen.getByRole("radiogroup")).not.toBeUndefined();
});

it("should fire 'value' property with text options", () => {
const { recordedEvents, onChange } = createChangeHandler();
render(
<RadioGroup
id="rg"
type={"RadioGroup"}
label={"Gender"}
value={"m"}
children={[
{ type: "Radio", value: "f", label: "Female" },
{ type: "Radio", value: "m", label: "Male" },
{ type: "Radio", value: "v", label: "Varying" },
]}
onChange={onChange}
/>,
);
fireEvent.click(screen.getByLabelText("Varying"));
expect(recordedEvents.length).toBe(1);
expect(recordedEvents[0]).toEqual({
componentType: "RadioGroup",
id: "rg",
property: "value",
value: "v",
});
fireEvent.click(screen.getByLabelText("Female"));
expect(recordedEvents.length).toBe(2);
expect(recordedEvents[1]).toEqual({
componentType: "RadioGroup",
id: "rg",
property: "value",
value: "f",
});
});
});
84 changes: 84 additions & 0 deletions chartlets.js/packages/lib/src/plugins/mui/RadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { type ChangeEvent } from "react";
import MuiRadio from "@mui/material/Radio";
import MuiRadioGroup from "@mui/material/RadioGroup";
import MuiFormControl from "@mui/material/FormControl";
import MuiFormControlLabel from "@mui/material/FormControlLabel";
import MuiFormLabel from "@mui/material/FormLabel";
import { Tooltip } from "./Tooltip";

import type { ComponentState, ComponentProps } from "@/index";

interface RadioState extends ComponentState {
type: "Radio";
value?: boolean | number | string | undefined;
label?: string;
size?: "medium" | "small" | string;
}

interface RadioGroupState extends ComponentState {
children?: RadioState[];
label?: string;
row?: boolean;
dense?: boolean;
tooltip?: string;
}

interface RadioGroupProps extends ComponentProps, RadioGroupState {}

export function RadioGroup({
type,
id,
name,
value,
disabled,
style,
label,
row,
tooltip,
dense,
children: radioButtons,
onChange,
}: RadioGroupProps) {
const handleChange = (
_event: ChangeEvent<HTMLInputElement>,
value: string,
) => {
if (id) {
return onChange({
componentType: type,
id: id,
property: "value",
value,
});
}
};
return (
<Tooltip title={tooltip}>
<MuiFormControl style={style} disabled={disabled}>
<MuiFormLabel>{label}</MuiFormLabel>
<MuiRadioGroup
id={id}
name={name}
row={row}
value={value}
onChange={handleChange}
>
{radioButtons &&
radioButtons.map((radioButton) => (
<MuiFormControlLabel
value={radioButton.value}
label={radioButton.label}
disabled={radioButton.disabled}
control={
<MuiRadio
id={radioButton.id}
size={dense ? "small" : "medium"}
/>
}
/>
))}
</MuiRadioGroup>
</MuiFormControl>
</Tooltip>
);
}
2 changes: 2 additions & 0 deletions chartlets.js/packages/lib/src/plugins/mui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Button } from "./Button";
import { Checkbox } from "./Checkbox";
import { CircularProgress } from "./CircularProgress";
import { IconButton } from "./IconButton";
import { RadioGroup } from "./RadioGroup";
import { Select } from "./Select";
import { Switch } from "./Switch";
import { Typography } from "./Typography";
Expand All @@ -16,6 +17,7 @@ export default function mui(): Plugin {
["Checkbox", Checkbox],
["CircularProgress", CircularProgress],
["IconButton", IconButton],
["RadioGroup", RadioGroup],
["Select", Select],
["Switch", Switch],
["Typography", Typography],
Expand Down
1 change: 1 addition & 0 deletions chartlets.py/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

* New components
- `Switch`
- `RadioGroup` and `Radio`

## Version 0.0.29 (from 2024/11/26)

Expand Down
2 changes: 2 additions & 0 deletions chartlets.py/chartlets/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from .progress import LinearProgress
from .progress import LinearProgressWithLabel
from .charts.vega import VegaChart
from .radiogroup import Radio
from .radiogroup import RadioGroup
from .select import Select
from .switch import Switch
from .typography import Typography
42 changes: 42 additions & 0 deletions chartlets.py/chartlets/components/radiogroup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from dataclasses import dataclass, field

from chartlets import Component


@dataclass(frozen=True)
class Radio(Component):
"""Select components are used for collecting user provided
information from a list of options."""

value: bool | int | float | str | None = None
"""The value of the component.
The DOM API casts this to a string.
"""

label: str | None = None
"""Button label. Optional."""


@dataclass(frozen=True)
class RadioGroup(Component):
"""The Radio Group allows the user to select one option from a set.

Use radio buttons when the user needs to see all available options.
If available options can be collapsed, consider using a `Select`
component because it uses less space.

Radio buttons should have the most commonly used option selected
by default.
"""

children: list[Radio] = field(default_factory=list)
"""The list of radio buttons."""

label: str | None = None
"""A label for the group. Optional"""

tooltip: str | None = None
"""Tooltip title. Optional."""

dense: bool | None = None
"""Dense styling with smaller radio buttons."""
28 changes: 28 additions & 0 deletions chartlets.py/tests/components/radiogroup_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from chartlets.components import RadioGroup, Radio
from tests.component_test import make_base


class RadioGroupTest(make_base(RadioGroup)):

def test_is_json_serializable(self):
self.assert_is_json_serializable(
self.cls(
label="Gender",
tooltip="Select your gender",
children=[
Radio(value="f", label="Female"),
Radio(value="m", label="Male"),
Radio(value="o", label="Other"),
],
),
{
"type": "RadioGroup",
"label": "Gender",
"tooltip": "Select your gender",
"children": [
{"type": "Radio", "value": "f", "label": "Female"},
{"type": "Radio", "value": "m", "label": "Male"},
{"type": "Radio", "value": "o", "label": "Other"},
],
},
)
Loading