Skip to content

Commit 889033a

Browse files
formanb-yogesh
andauthored
Added Tabs component (#81)
* Added Python `Tabs` class * Working now, demo needed still --------- Co-authored-by: Yogesh Kumar Baljeet Singh <[email protected]>
1 parent af749d2 commit 889033a

File tree

8 files changed

+178
-0
lines changed

8 files changed

+178
-0
lines changed

chartlets.js/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
- `LinearProgress`
4242
- `RadioGroup` and `Radio`
4343
- `Switch`
44+
- `Tabs`
4445

4546
* Supporting `tooltip` property for interactive MUI components.
4647

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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { IconButton } from "./IconButton";
77
import { RadioGroup } from "./RadioGroup";
88
import { Select } from "./Select";
99
import { Switch } from "./Switch";
10+
import { Tabs } from "./Tabs";
1011
import { Typography } from "./Typography";
1112

1213
export default function mui(): Plugin {
@@ -20,6 +21,7 @@ export default function mui(): Plugin {
2021
["RadioGroup", RadioGroup],
2122
["Select", Select],
2223
["Switch", Switch],
24+
["Tabs", Tabs],
2325
["Typography", Typography],
2426
],
2527
};

chartlets.py/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
* New components
2222
- `Switch`
2323
- `RadioGroup` and `Radio`
24+
- `Tabs`
2425

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

chartlets.py/chartlets/components/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@
1111
from .radiogroup import RadioGroup
1212
from .select import Select
1313
from .switch import Switch
14+
from .tabs import Tab
15+
from .tabs import Tabs
1416
from .typography import Typography
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from dataclasses import dataclass, field
2+
3+
from chartlets import Component
4+
5+
6+
@dataclass(frozen=True)
7+
class Tab(Component):
8+
"""The tab element itself.
9+
Clicking on a tab displays its corresponding panel.
10+
"""
11+
12+
icon: str | None = None
13+
"""The tab icon's name."""
14+
15+
label: str | None = None
16+
"""The tab label."""
17+
18+
disabled: bool | None = None
19+
"""Whether the tab is disabled."""
20+
21+
22+
@dataclass(frozen=True)
23+
class Tabs(Component):
24+
"""Tabs make it easy to explore and switch between different views.
25+
Tabs organize and allow navigation between groups of content that
26+
are related and at the same level of hierarchy.
27+
"""
28+
29+
value: int | None = None
30+
"""The currently selected tab index."""
31+
32+
children: list[str | Tab] = field(default_factory=list)
33+
"""The list of tab labels or `Tab` components."""
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from chartlets.components import Tabs, Tab
2+
from tests.component_test import make_base
3+
4+
5+
class TabsTest(make_base(Tabs)):
6+
7+
def test_is_json_serializable(self):
8+
self.assert_is_json_serializable(
9+
self.cls(children=["A", "B", "C"]),
10+
{"type": "Tabs", "children": ["A", "B", "C"]},
11+
)
12+
13+
self.assert_is_json_serializable(
14+
self.cls(
15+
value=1,
16+
children=[
17+
Tab(label="A"),
18+
Tab(icon="favorite"),
19+
Tab(label="C", disabled=True),
20+
],
21+
),
22+
{
23+
"type": "Tabs",
24+
"value": 1,
25+
"children": [
26+
{"type": "Tab", "label": "A"},
27+
{"type": "Tab", "icon": "favorite"},
28+
{"type": "Tab", "label": "C", "disabled": True},
29+
],
30+
},
31+
)

0 commit comments

Comments
 (0)