Skip to content

Commit

Permalink
Merge pull request #1974 from Avaiga/bug/#1911-current-page-menu
Browse files Browse the repository at this point in the history
Fix current page on menu
  • Loading branch information
namnguyen20999 authored Oct 30, 2024
2 parents aea1cee + c2b6175 commit 26e1125
Show file tree
Hide file tree
Showing 14 changed files with 287 additions and 103 deletions.
26 changes: 26 additions & 0 deletions doc/gui/examples/controls/menu_inactive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2021-2024 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
# -----------------------------------------------------------------------------------------
# To execute this script, make sure that the taipy-gui package is installed in your
# Python environment and run:
# python <script>
# -----------------------------------------------------------------------------------------
from taipy.gui import Gui

options = [("a", "Option A"), ("b", "Option B"), ("c", "Option C"), ("d", "Option D")]


page = """
<|menu|lov={options}|not active|>
"""

if __name__ == "__main__":
Gui(page).run(title="Menu - Inactive")
26 changes: 26 additions & 0 deletions doc/gui/examples/controls/menu_inactive_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2021-2024 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
# -----------------------------------------------------------------------------------------
# To execute this script, make sure that the taipy-gui package is installed in your
# Python environment and run:
# python <script>
# -----------------------------------------------------------------------------------------
from taipy.gui import Gui

options = [("a", "Option A"), ("b", "Option B"), ("c", "Option C"), ("d", "Option D")]
inactive_options = ["b", "d"]

page = """
<|menu|lov={options}|inactive_ids={inactive_options}|>
"""

if __name__ == "__main__":
Gui(page).run(title="Menu - Inactive options")
26 changes: 26 additions & 0 deletions doc/gui/examples/controls/menu_label.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2021-2024 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
# -----------------------------------------------------------------------------------------
# To execute this script, make sure that the taipy-gui package is installed in your
# Python environment and run:
# python <script>
# -----------------------------------------------------------------------------------------
from taipy.gui import Gui

options = [("a", "Option A"), ("b", "Option B"), ("c", "Option C"), ("d", "Option D")]


page = """
<|menu|label=menu|lov={options}|>
"""

if __name__ == "__main__":
Gui(page).run(title="Menu - Label")
30 changes: 30 additions & 0 deletions doc/gui/examples/controls/menu_on_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2021-2024 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
# -----------------------------------------------------------------------------------------
# To execute this script, make sure that the taipy-gui package is installed in your
# Python environment and run:
# python <script>
# -----------------------------------------------------------------------------------------
from taipy.gui import Gui

options = [("a", "Option A"), ("b", "Option B"), ("c", "Option C"), ("d", "Option D")]
selected = ["a", "b"]

def menu_action(state, id, payload):
if payload.get("args")[0] in state.selected:
print(f"Option {payload.get('args')[0]} is already selected") # noqa: F401, T201

page = """
<|menu|lov={options}|selected={selected}|on_action=menu_action|>
"""

if __name__ == "__main__":
Gui(page).run(title="Menu - On Action")
27 changes: 27 additions & 0 deletions doc/gui/examples/controls/menu_selected.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2021-2024 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
# -----------------------------------------------------------------------------------------
# To execute this script, make sure that the taipy-gui package is installed in your
# Python environment and run:
# python <script>
# -----------------------------------------------------------------------------------------
from taipy.gui import Gui

options = [("a", "Option A"), ("b", "Option B"), ("c", "Option C"), ("d", "Option D")]
selected = ["a", "b"]


page = """
<|menu|lov={options}|selected={selected}|>
"""

if __name__ == "__main__":
Gui(page).run(title="Menu - Selected")
26 changes: 26 additions & 0 deletions doc/gui/examples/controls/menu_simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2021-2024 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
# -----------------------------------------------------------------------------------------
# To execute this script, make sure that the taipy-gui package is installed in your
# Python environment and run:
# python <script>
# -----------------------------------------------------------------------------------------
from taipy.gui import Gui

options = [("a", "Option A"), ("b", "Option B"), ("c", "Option C"), ("d", "Option D")]


page = """
<|menu|lov={options}|>
"""

if __name__ == "__main__":
Gui(page).run(title="Menu - Simple")
33 changes: 14 additions & 19 deletions frontend/taipy-gui/src/components/Taipy/Menu.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
/*
* Copyright 2021-2024 Avaiga Private Limited
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

import React from "react";
import { render } from "@testing-library/react";
import "@testing-library/jest-dom";
Expand All @@ -22,58 +9,66 @@ import { TaipyContext } from "../../context/taipyContext";
import { LovItem } from "../../utils/lov";

const lov: LovItem[] = [
{id: "id1", item: "Item 1"},
{id: "id2", item:"Item 2"},
{id: "id3", item:"Item 3"},
{id: "id4", item:"Item 4"},
{ id: "id1", item: "Item 1" },
{ id: "id2", item: "Item 2" },
{ id: "id3", item: "Item 3" },
{ id: "id4", item: "Item 4" },
];

const imageItem: LovItem = {id: "ii1", item: { path: "/img/fred.png", text: "Image" }};
const imageItem: LovItem = { id: "ii1", item: { path: "/img/fred.png", text: "Image" } };

describe("Menu Component", () => {
it("renders", async () => {
const { getByText } = render(<Menu lov={lov} />);
const elt = getByText("Item 1");
expect(elt.tagName).toBe("SPAN");
});

it("uses the class", async () => {
const { getByText } = render(<Menu lov={lov} className="taipy-menu" />);
const elt = getByText("Item 1");
expect(elt.closest(".taipy-menu")).not.toBeNull();
});

it("can display an avatar with initials", async () => {
const lovWithImage = [...lov, imageItem];
const { getByText } = render(<Menu lov={lovWithImage} />);
const elt = getByText("I2");
expect(elt.tagName).toBe("DIV");
});

it("can display an image", async () => {
const lovWithImage = [...lov, imageItem];
const { getByAltText } = render(<Menu lov={lovWithImage} />);
const elt = getByAltText("Image");
expect(elt.tagName).toBe("IMG");
});

it("is disabled", async () => {
const { getAllByRole } = render(<Menu lov={lov} active={false} />);
const elts = getAllByRole("button");
elts.forEach((elt, idx) => idx > 0 && expect(elt).toHaveClass("Mui-disabled"));
});

it("is enabled by default", async () => {
const { getAllByRole } = render(<Menu lov={lov} />);
const elts = getAllByRole("button");
elts.forEach((elt) => expect(elt).not.toHaveClass("Mui-disabled"));
});

it("is enabled by active", async () => {
const { getAllByRole } = render(<Menu lov={lov} active={true} />);
const elts = getAllByRole("button");
elts.forEach((elt) => expect(elt).not.toHaveClass("Mui-disabled"));
});

it("can disable a specific item", async () => {
const { getByText } = render(<Menu lov={lov} inactiveIds={[lov[0].id]} />);
const elt = getByText(lov[0].item as string);
const button = elt.closest('[role="button"]')
const button = elt.closest('[role="button"]');
expect(button).toHaveClass("Mui-disabled");
});

it("dispatch a well formed message", async () => {
const dispatch = jest.fn();
const state: TaipyState = INITIAL_STATE;
Expand Down
50 changes: 34 additions & 16 deletions frontend/taipy-gui/src/components/Taipy/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import Avatar from "@mui/material/Avatar";
import CardHeader from "@mui/material/CardHeader";
import ListItemAvatar from "@mui/material/ListItemAvatar";
import Box from "@mui/material/Box";
import Tooltip from '@mui/material/Tooltip';
import Tooltip from "@mui/material/Tooltip";
import { Theme, useTheme } from "@mui/system";

import { SingleItem } from "./lovUtils";
Expand All @@ -37,7 +37,7 @@ const baseTitleProps = { noWrap: true, variant: "h6" } as const;

const Menu = (props: MenuProps) => {
const { label, onAction = "", lov, width, inactiveIds = emptyArray, active = true } = props;
const [selectedValue, setSelectedValue] = useState("");
const [selectedValue, setSelectedValue] = useState<string>("");
const [opened, setOpened] = useState(false);
const dispatch = useDispatch();
const theme = useTheme();
Expand All @@ -55,41 +55,59 @@ const Menu = (props: MenuProps) => {
});
}
},
[onAction, dispatch, active, module]
[active, dispatch, module, onAction]
);

const openHandler = useCallback((evt: MouseEvent<HTMLElement>) => {
evt.stopPropagation();
setOpened((o) => !o);
}, []);

const selected = useMemo(() => {
const selected = Array.isArray(props.selected) ? props.selected : [];
if (selectedValue && !selected.includes(selectedValue)) {
return [...selected, selectedValue];
}
return selected;
}, [props.selected, selectedValue]);

const [drawerSx, titleProps] = useMemo(() => {
const drawerWidth = opened ? width : `calc(${theme.spacing(9)} + 1px)`;
const titleWidth = opened ? `calc(${width} - ${theme.spacing(10)})`: undefined;
return [{
width: drawerWidth,
flexShrink: 0,
"& .MuiDrawer-paper": {
const titleWidth = opened ? `calc(${width} - ${theme.spacing(10)})` : undefined;
return [
{
width: drawerWidth,
boxSizing: "border-box",
flexShrink: 0,
"& .MuiDrawer-paper": {
width: drawerWidth,
boxSizing: "border-box",
transition: "width 0.3s",
},
transition: "width 0.3s",
},
transition: "width 0.3s",
}, {...baseTitleProps, width: titleWidth}];
{ ...baseTitleProps, width: titleWidth },
];
}, [opened, width, theme]);

return lov && lov.length ? (
<Drawer variant="permanent" anchor="left" sx={drawerSx} className={`${className} ${getComponentClassName(props.children)}`}>
<Drawer
variant="permanent"
anchor="left"
sx={drawerSx}
className={`${className} ${getComponentClassName(props.children)}`}
>
<Box style={boxDrawerStyle}>
<List>
<ListItemButton key="taipy_menu_0" onClick={openHandler}>
<ListItemAvatar>
<CardHeader
sx={headerSx}
avatar={
<Tooltip title={label || false}><Avatar sx={avatarSx}>
<MenuIco />
</Avatar></Tooltip>
<Tooltip title={label || false}>
<Avatar sx={avatarSx}>
<MenuIco />
</Avatar>
</Tooltip>
}
title={label}
titleTypographyProps={titleProps}
Expand All @@ -101,7 +119,7 @@ const Menu = (props: MenuProps) => {
key={elt.id}
value={elt.id}
item={elt.item}
selectedValue={selectedValue}
selectedValue={selected}
clickHandler={clickHandler}
disabled={!active || inactiveIds.includes(elt.id)}
withAvatar={true}
Expand Down
1 change: 1 addition & 0 deletions frontend/taipy-gui/src/components/Taipy/MenuCtl.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ describe("MenuCtl Component", () => {
},
],
onAction: "on_action",
selected: [],
width: "15vw",
},
type: "SET_MENU",
Expand Down
Loading

0 comments on commit 26e1125

Please sign in to comment.