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 @@ -41,6 +41,7 @@
- `LinearProgress`
- `RadioGroup` and `Radio`
- `Switch`
- `Tabs`

* Supporting `tooltip` property for interactive MUI components.

Expand Down
49 changes: 49 additions & 0 deletions chartlets.js/packages/lib/src/plugins/mui/Tabs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, it, expect } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { createChangeHandler } from "./common.test";
import { Tabs } from "./Tabs";

describe("Tabs", () => {
it("should render the Tabs component", () => {
render(
<Tabs
id="tbs"
type={"Tabs"}
onChange={() => {}}
children={["Datasets", "Variables"]}
/>,
);
// to inspect rendered component, do:
// expect(document.querySelector("#tbs")).toEqual({});
expect(screen.getByText("Datasets")).not.toBeUndefined();
});

it("should fire 'value' property", () => {
const { recordedEvents, onChange } = createChangeHandler();
render(
<Tabs
id="tbs"
type={"Tabs"}
value={0}
onChange={onChange}
children={["Datasets", "Variables", { type: "Tab", label: "Stats" }]}
/>,
);
fireEvent.click(screen.getByText("Variables"));
expect(recordedEvents.length).toEqual(1);
expect(recordedEvents[0]).toEqual({
componentType: "Tabs",
id: "tbs",
property: "value",
value: 1,
});
fireEvent.click(screen.getByText("Stats"));
expect(recordedEvents.length).toEqual(2);
expect(recordedEvents[1]).toEqual({
componentType: "Tabs",
id: "tbs",
property: "value",
value: 2,
});
});
});
59 changes: 59 additions & 0 deletions chartlets.js/packages/lib/src/plugins/mui/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import MuiIcon from "@mui/material/Icon";
import MuiTabs from "@mui/material/Tabs";
import MuiTab from "@mui/material/Tab";

import type { ComponentProps, ComponentState } from "@/index";
import type { SyntheticEvent } from "react";
import { isString } from "@/utils/isString";
import { isComponentState } from "@/types/state/component";

interface TabState {
type: "Tab";
label?: string;
icon?: string;
disabled?: boolean;
}

interface TabsState extends ComponentState {
value?: number;
children?: (string | TabState)[];
}

interface TabsProps extends ComponentProps, TabsState {}

export function Tabs({
type,
id,
value,
children: tabItems,
disabled,
style,
onChange,
}: TabsProps) {
const handleChange = (_event: SyntheticEvent, value: number) => {
if (id) {
onChange({
componentType: type,
id: id,
property: "value",
value: value,
});
}
};
return (
<MuiTabs id={id} style={style} value={value} onChange={handleChange}>
{tabItems?.map((tab) => {
const tabState = isComponentState(tab) ? (tab as TabState) : undefined;
return (
<MuiTab
label={tabState ? tabState.label : isString(tab) ? tab : ""}
icon={
tabState && tabState.icon && <MuiIcon>{tabState.icon}</MuiIcon>
}
disabled={disabled || (tabState && tabState.disabled)}
/>
);
})}
</MuiTabs>
);
}
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 @@ -7,6 +7,7 @@ import { IconButton } from "./IconButton";
import { RadioGroup } from "./RadioGroup";
import { Select } from "./Select";
import { Switch } from "./Switch";
import { Tabs } from "./Tabs";
import { Typography } from "./Typography";

export default function mui(): Plugin {
Expand All @@ -20,6 +21,7 @@ export default function mui(): Plugin {
["RadioGroup", RadioGroup],
["Select", Select],
["Switch", Switch],
["Tabs", Tabs],
["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 @@ -21,6 +21,7 @@
* New components
- `Switch`
- `RadioGroup` and `Radio`
- `Tabs`

## 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 @@ -11,4 +11,6 @@
from .radiogroup import RadioGroup
from .select import Select
from .switch import Switch
from .tabs import Tab
from .tabs import Tabs
from .typography import Typography
33 changes: 33 additions & 0 deletions chartlets.py/chartlets/components/tabs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from dataclasses import dataclass, field

from chartlets import Component


@dataclass(frozen=True)
class Tab(Component):
"""The tab element itself.
Clicking on a tab displays its corresponding panel.
"""

icon: str | None = None
"""The tab icon's name."""

label: str | None = None
"""The tab label."""

disabled: bool | None = None
"""Whether the tab is disabled."""


@dataclass(frozen=True)
class Tabs(Component):
"""Tabs make it easy to explore and switch between different views.
Tabs organize and allow navigation between groups of content that
are related and at the same level of hierarchy.
"""

value: int | None = None
"""The currently selected tab index."""

children: list[str | Tab] = field(default_factory=list)
"""The list of tab labels or `Tab` components."""
31 changes: 31 additions & 0 deletions chartlets.py/tests/components/tabs_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from chartlets.components import Tabs, Tab
from tests.component_test import make_base


class TabsTest(make_base(Tabs)):

def test_is_json_serializable(self):
self.assert_is_json_serializable(
self.cls(children=["A", "B", "C"]),
{"type": "Tabs", "children": ["A", "B", "C"]},
)

self.assert_is_json_serializable(
self.cls(
value=1,
children=[
Tab(label="A"),
Tab(icon="favorite"),
Tab(label="C", disabled=True),
],
),
{
"type": "Tabs",
"value": 1,
"children": [
{"type": "Tab", "label": "A"},
{"type": "Tab", "icon": "favorite"},
{"type": "Tab", "label": "C", "disabled": True},
],
},
)
Loading