Skip to content

Commit af749d2

Browse files
authored
Added RadioGroup and Radio components (#82)
* added `RadioGroup` and `Radio` * strange fix
1 parent e012b79 commit af749d2

File tree

8 files changed

+222
-0
lines changed

8 files changed

+222
-0
lines changed

chartlets.js/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
* New (MUI) components
4141
- `LinearProgress`
42+
- `RadioGroup` and `Radio`
4243
- `Switch`
4344

4445
* Supporting `tooltip` property for interactive MUI components.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { describe, it, expect } from "vitest";
2+
import { render, screen, fireEvent } from "@testing-library/react";
3+
import { createChangeHandler } from "./common.test";
4+
import { RadioGroup } from "./RadioGroup";
5+
6+
describe("RadioGroup", () => {
7+
it("should render the component", () => {
8+
render(
9+
<RadioGroup
10+
id="rg"
11+
type={"RadioGroup"}
12+
label={"Gender"}
13+
value={"f"}
14+
children={[
15+
{ type: "Radio", value: "f", label: "Female" },
16+
{ type: "Radio", value: "m", label: "Male" },
17+
{ type: "Radio", value: "v", label: "Varying" },
18+
]}
19+
onChange={() => {}}
20+
row
21+
dense
22+
/>,
23+
);
24+
// to inspect rendered component, do:
25+
// expect(document.querySelector("#rg")).toEqual({});
26+
expect(screen.getByRole("radiogroup")).not.toBeUndefined();
27+
});
28+
29+
it("should fire 'value' property with text options", () => {
30+
const { recordedEvents, onChange } = createChangeHandler();
31+
render(
32+
<RadioGroup
33+
id="rg"
34+
type={"RadioGroup"}
35+
label={"Gender"}
36+
value={"m"}
37+
children={[
38+
{ type: "Radio", value: "f", label: "Female" },
39+
{ type: "Radio", value: "m", label: "Male" },
40+
{ type: "Radio", value: "v", label: "Varying" },
41+
]}
42+
onChange={onChange}
43+
/>,
44+
);
45+
fireEvent.click(screen.getByLabelText("Varying"));
46+
expect(recordedEvents.length).toBe(1);
47+
expect(recordedEvents[0]).toEqual({
48+
componentType: "RadioGroup",
49+
id: "rg",
50+
property: "value",
51+
value: "v",
52+
});
53+
fireEvent.click(screen.getByLabelText("Female"));
54+
expect(recordedEvents.length).toBe(2);
55+
expect(recordedEvents[1]).toEqual({
56+
componentType: "RadioGroup",
57+
id: "rg",
58+
property: "value",
59+
value: "f",
60+
});
61+
});
62+
});
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { type ChangeEvent } from "react";
2+
import MuiRadio from "@mui/material/Radio";
3+
import MuiRadioGroup from "@mui/material/RadioGroup";
4+
import MuiFormControl from "@mui/material/FormControl";
5+
import MuiFormControlLabel from "@mui/material/FormControlLabel";
6+
import MuiFormLabel from "@mui/material/FormLabel";
7+
import { Tooltip } from "./Tooltip";
8+
9+
import type { ComponentState, ComponentProps } from "@/index";
10+
11+
interface RadioState extends ComponentState {
12+
type: "Radio";
13+
value?: boolean | number | string | undefined;
14+
label?: string;
15+
size?: "medium" | "small" | string;
16+
}
17+
18+
interface RadioGroupState extends ComponentState {
19+
children?: RadioState[];
20+
label?: string;
21+
row?: boolean;
22+
dense?: boolean;
23+
tooltip?: string;
24+
}
25+
26+
interface RadioGroupProps extends ComponentProps, RadioGroupState {}
27+
28+
export function RadioGroup({
29+
type,
30+
id,
31+
name,
32+
value,
33+
disabled,
34+
style,
35+
label,
36+
row,
37+
tooltip,
38+
dense,
39+
children: radioButtons,
40+
onChange,
41+
}: RadioGroupProps) {
42+
const handleChange = (
43+
_event: ChangeEvent<HTMLInputElement>,
44+
value: string,
45+
) => {
46+
if (id) {
47+
return onChange({
48+
componentType: type,
49+
id: id,
50+
property: "value",
51+
value,
52+
});
53+
}
54+
};
55+
return (
56+
<Tooltip title={tooltip}>
57+
<MuiFormControl style={style} disabled={disabled}>
58+
<MuiFormLabel>{label}</MuiFormLabel>
59+
<MuiRadioGroup
60+
id={id}
61+
name={name}
62+
row={row}
63+
value={value}
64+
onChange={handleChange}
65+
>
66+
{radioButtons &&
67+
radioButtons.map((radioButton) => (
68+
<MuiFormControlLabel
69+
value={radioButton.value}
70+
label={radioButton.label}
71+
disabled={radioButton.disabled}
72+
control={
73+
<MuiRadio
74+
id={radioButton.id}
75+
size={dense ? "small" : "medium"}
76+
/>
77+
}
78+
/>
79+
))}
80+
</MuiRadioGroup>
81+
</MuiFormControl>
82+
</Tooltip>
83+
);
84+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Button } from "./Button";
44
import { Checkbox } from "./Checkbox";
55
import { CircularProgress } from "./CircularProgress";
66
import { IconButton } from "./IconButton";
7+
import { RadioGroup } from "./RadioGroup";
78
import { Select } from "./Select";
89
import { Switch } from "./Switch";
910
import { Typography } from "./Typography";
@@ -16,6 +17,7 @@ export default function mui(): Plugin {
1617
["Checkbox", Checkbox],
1718
["CircularProgress", CircularProgress],
1819
["IconButton", IconButton],
20+
["RadioGroup", RadioGroup],
1921
["Select", Select],
2022
["Switch", Switch],
2123
["Typography", Typography],

chartlets.py/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
* New components
2222
- `Switch`
23+
- `RadioGroup` and `Radio`
2324

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

chartlets.py/chartlets/components/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from .progress import LinearProgress
88
from .progress import LinearProgressWithLabel
99
from .charts.vega import VegaChart
10+
from .radiogroup import Radio
11+
from .radiogroup import RadioGroup
1012
from .select import Select
1113
from .switch import Switch
1214
from .typography import Typography
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from dataclasses import dataclass, field
2+
3+
from chartlets import Component
4+
5+
6+
@dataclass(frozen=True)
7+
class Radio(Component):
8+
"""Select components are used for collecting user provided
9+
information from a list of options."""
10+
11+
value: bool | int | float | str | None = None
12+
"""The value of the component.
13+
The DOM API casts this to a string.
14+
"""
15+
16+
label: str | None = None
17+
"""Button label. Optional."""
18+
19+
20+
@dataclass(frozen=True)
21+
class RadioGroup(Component):
22+
"""The Radio Group allows the user to select one option from a set.
23+
24+
Use radio buttons when the user needs to see all available options.
25+
If available options can be collapsed, consider using a `Select`
26+
component because it uses less space.
27+
28+
Radio buttons should have the most commonly used option selected
29+
by default.
30+
"""
31+
32+
children: list[Radio] = field(default_factory=list)
33+
"""The list of radio buttons."""
34+
35+
label: str | None = None
36+
"""A label for the group. Optional"""
37+
38+
tooltip: str | None = None
39+
"""Tooltip title. Optional."""
40+
41+
dense: bool | None = None
42+
"""Dense styling with smaller radio buttons."""
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from chartlets.components import RadioGroup, Radio
2+
from tests.component_test import make_base
3+
4+
5+
class RadioGroupTest(make_base(RadioGroup)):
6+
7+
def test_is_json_serializable(self):
8+
self.assert_is_json_serializable(
9+
self.cls(
10+
label="Gender",
11+
tooltip="Select your gender",
12+
children=[
13+
Radio(value="f", label="Female"),
14+
Radio(value="m", label="Male"),
15+
Radio(value="o", label="Other"),
16+
],
17+
),
18+
{
19+
"type": "RadioGroup",
20+
"label": "Gender",
21+
"tooltip": "Select your gender",
22+
"children": [
23+
{"type": "Radio", "value": "f", "label": "Female"},
24+
{"type": "Radio", "value": "m", "label": "Male"},
25+
{"type": "Radio", "value": "o", "label": "Other"},
26+
],
27+
},
28+
)

0 commit comments

Comments
 (0)