Skip to content

Commit 6297274

Browse files
committed
Merge branch 'main' into yogesh-41-slider
2 parents 3d1d146 + e55810d commit 6297274

File tree

14 files changed

+403
-1
lines changed

14 files changed

+403
-1
lines changed

.github/workflows/backend-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,6 @@ jobs:
2727
uses: codecov/codecov-action@v4
2828
with:
2929
directory: chartlets.py/
30+
flags: backend
3031
verbose: true
3132
token: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/frontend-ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ jobs:
4040
- name: Upload coverage reports for lib to Codecov
4141
uses: codecov/codecov-action@v4
4242
with:
43-
directory: chartlets.js/packages/lib/
43+
directory: chartlets.js/packages/lib/coverage/
44+
flags: frontend
4445
verbose: true
4546
token: ${{ secrets.CODECOV_TOKEN }}
4647

chartlets.js/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939

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

4446
* Supporting `tooltip` property for interactive MUI components.
4547

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+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { describe, it, expect } from "vitest";
2+
import { render, screen, fireEvent } from "@testing-library/react";
3+
import { createChangeHandler } from "./common.test";
4+
import { Tabs } from "./Tabs";
5+
6+
describe("Tabs", () => {
7+
it("should render the Tabs component", () => {
8+
render(
9+
<Tabs
10+
id="tbs"
11+
type={"Tabs"}
12+
onChange={() => {}}
13+
children={["Datasets", "Variables"]}
14+
/>,
15+
);
16+
// to inspect rendered component, do:
17+
// expect(document.querySelector("#tbs")).toEqual({});
18+
expect(screen.getByText("Datasets")).not.toBeUndefined();
19+
});
20+
21+
it("should fire 'value' property", () => {
22+
const { recordedEvents, onChange } = createChangeHandler();
23+
render(
24+
<Tabs
25+
id="tbs"
26+
type={"Tabs"}
27+
value={0}
28+
onChange={onChange}
29+
children={["Datasets", "Variables", { type: "Tab", label: "Stats" }]}
30+
/>,
31+
);
32+
fireEvent.click(screen.getByText("Variables"));
33+
expect(recordedEvents.length).toEqual(1);
34+
expect(recordedEvents[0]).toEqual({
35+
componentType: "Tabs",
36+
id: "tbs",
37+
property: "value",
38+
value: 1,
39+
});
40+
fireEvent.click(screen.getByText("Stats"));
41+
expect(recordedEvents.length).toEqual(2);
42+
expect(recordedEvents[1]).toEqual({
43+
componentType: "Tabs",
44+
id: "tbs",
45+
property: "value",
46+
value: 2,
47+
});
48+
});
49+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import MuiIcon from "@mui/material/Icon";
2+
import MuiTabs from "@mui/material/Tabs";
3+
import MuiTab from "@mui/material/Tab";
4+
5+
import type { ComponentProps, ComponentState } from "@/index";
6+
import type { SyntheticEvent } from "react";
7+
import { isString } from "@/utils/isString";
8+
import { isComponentState } from "@/types/state/component";
9+
10+
interface TabState {
11+
type: "Tab";
12+
label?: string;
13+
icon?: string;
14+
disabled?: boolean;
15+
}
16+
17+
interface TabsState extends ComponentState {
18+
value?: number;
19+
children?: (string | TabState)[];
20+
}
21+
22+
interface TabsProps extends ComponentProps, TabsState {}
23+
24+
export function Tabs({
25+
type,
26+
id,
27+
value,
28+
children: tabItems,
29+
disabled,
30+
style,
31+
onChange,
32+
}: TabsProps) {
33+
const handleChange = (_event: SyntheticEvent, value: number) => {
34+
if (id) {
35+
onChange({
36+
componentType: type,
37+
id: id,
38+
property: "value",
39+
value: value,
40+
});
41+
}
42+
};
43+
return (
44+
<MuiTabs id={id} style={style} value={value} onChange={handleChange}>
45+
{tabItems?.map((tab) => {
46+
const tabState = isComponentState(tab) ? (tab as TabState) : undefined;
47+
return (
48+
<MuiTab
49+
label={tabState ? tabState.label : isString(tab) ? tab : ""}
50+
icon={
51+
tabState && tabState.icon && <MuiIcon>{tabState.icon}</MuiIcon>
52+
}
53+
disabled={disabled || (tabState && tabState.disabled)}
54+
/>
55+
);
56+
})}
57+
</MuiTabs>
58+
);
59+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ 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";
10+
import { Tabs } from "./Tabs";
911
import { Typography } from "./Typography";
1012

1113
export default function mui(): Plugin {
@@ -16,8 +18,10 @@ export default function mui(): Plugin {
1618
["Checkbox", Checkbox],
1719
["CircularProgress", CircularProgress],
1820
["IconButton", IconButton],
21+
["RadioGroup", RadioGroup],
1922
["Select", Select],
2023
["Switch", Switch],
24+
["Tabs", Tabs],
2125
["Typography", Typography],
2226
],
2327
};

chartlets.py/CHANGES.md

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

2121
* New components
2222
- `Switch`
23+
- `RadioGroup` and `Radio`
24+
- `Tabs`
2325

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

chartlets.py/chartlets/components/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
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
14+
from .tabs import Tab
15+
from .tabs import Tabs
1216
from .typography import Typography

0 commit comments

Comments
 (0)