diff --git a/doc/gui/examples/controls/status_icons.py b/doc/gui/examples/controls/status_icons.py new file mode 100644 index 000000000..af7d62edd --- /dev/null +++ b/doc/gui/examples/controls/status_icons.py @@ -0,0 +1,16 @@ +from taipy.gui import Gui + +status = [ + ("warning", "Task is launched."), + ("warning", "Tasks is waiting."), + ("error", "Task timeout."), + ("success", "Task Succeeded"), + ("info", "Process was cancelled.") +] + +page = """ +<|{status}|status|use_icon|> +""" + +if __name__ == "__main__": + Gui(page).run(title="Status - With icons") diff --git a/doc/gui/examples/controls/status_mixed_icons.py b/doc/gui/examples/controls/status_mixed_icons.py new file mode 100644 index 000000000..94182bfe3 --- /dev/null +++ b/doc/gui/examples/controls/status_mixed_icons.py @@ -0,0 +1,20 @@ +from taipy.gui import Gui + +status = [ + ("warning", "Task is launched."), + ("warning", "Tasks is waiting."), + ("error", "Task timeout."), + ("success", "Task Succeeded"), + ("info", "Process was cancelled.") +] + +# Info: svg icon (pants.svg) +# success: no icon +# warning: default icon +# error: inline svg icon (red disc) +page = """ +<|{status}|status|don't use_icon|use_icon[info]=https://www.svgrepo.com/show/530594/pants.svg|use_icon[success]|use_icon[error]=|> +""" # noqa: E501 + +if __name__ == "__main__": + Gui(page).run(title="Status - With mixed icons") diff --git a/doc/gui/examples/controls/status_simple.py b/doc/gui/examples/controls/status_simple.py new file mode 100644 index 000000000..79cab030b --- /dev/null +++ b/doc/gui/examples/controls/status_simple.py @@ -0,0 +1,16 @@ +from taipy.gui import Gui + +status = [ + ("warning", "Task is launched."), + ("warning", "Tasks is waiting."), + ("error", "Task timeout."), + ("success", "Task Succeeded"), + ("info", "Process was cancelled.") +] + +page = """ +<|{status}|status|> +""" + +if __name__ == "__main__": + Gui(page).run(title="Status - Simple") diff --git a/doc/gui/examples/controls/status_svg_icons.py b/doc/gui/examples/controls/status_svg_icons.py new file mode 100644 index 000000000..3df3ab375 --- /dev/null +++ b/doc/gui/examples/controls/status_svg_icons.py @@ -0,0 +1,20 @@ +from taipy.gui import Gui + +status = [ + ("warning", "Task is launched."), + ("warning", "Tasks is waiting."), + ("error", "Task timeout."), + ("success", "Task Succeeded"), + ("info", "Process was cancelled.") +] + +# Info: svg icon (pants.svg) +# success: svg icon (hotel.svg) +# warning: svg icon (diving-goggles.svg) +# error: svg icon (hat.svg) +page = """ +<|{status}|status|use_icon[info]=https://www.svgrepo.com/show/530594/pants.svg|use_icon[success]=https://www.svgrepo.com/show/530595/hotel.svg|use_icon[warning]=https://www.svgrepo.com/show/530596/diving-goggles.svg|use_icon[error]=https://www.svgrepo.com/show/530597/hat.svg|> +""" + +if __name__ == "__main__": + Gui(page).run(title="Status - With SVG icons") diff --git a/doc/gui/extension/example_library/example_library.py b/doc/gui/extension/example_library/example_library.py index c66c7f870..acba0aeca 100644 --- a/doc/gui/extension/example_library/example_library.py +++ b/doc/gui/extension/example_library/example_library.py @@ -42,6 +42,16 @@ def __init__(self) -> None: # element, exported as GameTable in front-end/src/index.ts # react_component="GameTable", ), + "visual_label_list": Element( + "lov", + { + "lov": ElementProperty(PropertyType.lov), + "sort": ElementProperty(PropertyType.string), + }, + # The name of the React component (VisualLabelList) that implements this custom + # element, exported as LabeledItemList in front-end/src/index.ts + react_component="VisualLabelList", + ) } # The implementation of the rendering for the "fraction" static element diff --git a/doc/gui/extension/example_library/front-end/src/VisualLabelList.tsx b/doc/gui/extension/example_library/front-end/src/VisualLabelList.tsx new file mode 100644 index 000000000..793a6499a --- /dev/null +++ b/doc/gui/extension/example_library/front-end/src/VisualLabelList.tsx @@ -0,0 +1,50 @@ +import React, { useMemo } from "react"; +import { LoV, useLovListMemo } from "taipy-gui"; + +interface VisualLabelListProps { + lov?: LoV; + defaultLov?: string; + sort?: "asc" | "desc"; +} + +const styles = { + listItem: { + display: "flex", + alignItems: "center", + }, + image: { + marginRight: "8px", + width: "1em", + height: "1em", + }, +}; + +const VisualLabelList: React.FC = ({ lov, defaultLov = "", sort }) => { + const lovList = useLovListMemo(lov, defaultLov); + + const sortedLovList = useMemo(() => { + if (sort) { + return lovList.slice().sort((a, b) => { + return sort === "asc" ? a.id.localeCompare(b.id) : b.id.localeCompare(a.id); + }); + } + return lovList; + }, [lovList, sort]); + + return ( +
+ +
+ ); +}; + +export default VisualLabelList; diff --git a/doc/gui/extension/example_library/front-end/src/index.ts b/doc/gui/extension/example_library/front-end/src/index.ts index c7b47323e..c2de08c12 100644 --- a/doc/gui/extension/example_library/front-end/src/index.ts +++ b/doc/gui/extension/example_library/front-end/src/index.ts @@ -8,5 +8,6 @@ // the name used in the element declaration in the element library. import ColoredLabel from "./ColoredLabel"; import GameTable from "./GameTable"; +import VisualLabelList from "./VisualLabelList"; -export { ColoredLabel as ExampleLabel, GameTable }; +export { ColoredLabel as ExampleLabel, GameTable, VisualLabelList }; diff --git a/doc/gui/extension/images/cpp.png b/doc/gui/extension/images/cpp.png new file mode 100644 index 000000000..a02e2eaa6 Binary files /dev/null and b/doc/gui/extension/images/cpp.png differ diff --git a/doc/gui/extension/images/java.png b/doc/gui/extension/images/java.png new file mode 100644 index 000000000..78fa9c5d7 Binary files /dev/null and b/doc/gui/extension/images/java.png differ diff --git a/doc/gui/extension/images/javascript.png b/doc/gui/extension/images/javascript.png new file mode 100644 index 000000000..9f148627c Binary files /dev/null and b/doc/gui/extension/images/javascript.png differ diff --git a/doc/gui/extension/images/python.png b/doc/gui/extension/images/python.png new file mode 100644 index 000000000..a29196d44 Binary files /dev/null and b/doc/gui/extension/images/python.png differ diff --git a/doc/gui/extension/images/typescript.png b/doc/gui/extension/images/typescript.png new file mode 100644 index 000000000..ec4a95e24 Binary files /dev/null and b/doc/gui/extension/images/typescript.png differ diff --git a/doc/gui/extension/visual_label_list.py b/doc/gui/extension/visual_label_list.py new file mode 100644 index 000000000..73cc5efa5 --- /dev/null +++ b/doc/gui/extension/visual_label_list.py @@ -0,0 +1,28 @@ +# 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. +from example_library import ExampleLibrary + +from taipy.gui import Gui, Icon + +languages = [ + ["Python", Icon("images/python.png", "Python logo")], + ["JavaScript", Icon("images/javascript.png", "JavaScript logo")], + ["TypeScript", Icon("images/typescript.png", "TypeScript logo")], + ["Java", Icon("images/java.png", "Java logo")], + ["C++", Icon("images/cpp.png", "C++ logo")], +] + +page = """ +<|{languages}|example.visual_label_list|sort=asc|> +""" + +if __name__ == "__main__": + Gui(page, libraries=[ExampleLibrary()]).run(title="List of item") diff --git a/frontend/taipy-gui/src/components/Taipy/Status.spec.tsx b/frontend/taipy-gui/src/components/Taipy/Status.spec.tsx index 072472a0c..d584fa6fa 100644 --- a/frontend/taipy-gui/src/components/Taipy/Status.spec.tsx +++ b/frontend/taipy-gui/src/components/Taipy/Status.spec.tsx @@ -42,14 +42,14 @@ describe("Status Component", () => { await userEvent.click(elt); expect(myClose).toHaveBeenCalled(); }) - it("displays the icon", async () => { - const {getByTestId} = render(} onClose={jest.fn()} />); + it("displays the open icon", async () => { + const {getByTestId} = render(} onClose={jest.fn()} />); getByTestId("PlusOneOutlinedIcon"); }) // Test case for Inline SVG content it("renders an Avatar with inline SVG", () => { const inlineSvg = ""; - const { getByTestId } = render(); + const { getByTestId } = render(); const avatar = getByTestId("Avatar"); // Inline SVG should be rendered as inner HTML inside the Avatar const svgElement = avatar.querySelector("svg"); @@ -57,44 +57,44 @@ describe("Status Component", () => { }); // Test case for Text content (default behavior) - it("renders Avatar with initial when content is text", () => { - const { getByTestId } = render(); + it("renders Avatar with initial when icon is text", () => { + const { getByTestId } = render(); const avatar = getByTestId("Avatar"); expect(avatar).toHaveTextContent("S"); }); // Test case for empty content - it("renders Avatar with initial when no content is provided", () => { - const { getByTestId } = render(); + it("renders Avatar with initial when no icon is provided", () => { + const { getByTestId } = render(); const avatar = getByTestId("Avatar"); expect(avatar).toHaveTextContent("S"); }); // Test case for an invalid content type (like a non-SVG string) - it("renders Avatar with initial if content is invalid", () => { - const { getByTestId } = render(); + it("renders Avatar with initial if icon is invalid", () => { + const { getByTestId } = render(); const avatar = getByTestId("Avatar"); expect(avatar).toHaveTextContent("S"); }); - it("renders an avatar with initial when withIcons is false", () => { + it("renders an avatar with initial when icon is false", () => { const statusWithoutIcons: StatusType = { status: "warning", message: "Warning detected" }; - - const { getByTestId } = render(); - + + const { getByTestId } = render(); + // Check if the avatar has the initial of the status (W) const avatar = getByTestId("Avatar"); expect(avatar).toHaveTextContent("W"); }); - it("renders the correct icon when withIcons is true", () => { + it("renders the correct icon when icon is true", () => { const statusWithIcons: StatusType = { status: "success", message: "Operation successful" }; - - const { getByTestId } = render(); - + + const { getByTestId } = render(); + // Check if the Avatar element contains the icon (CheckCircleIcon for success status) const avatar = getByTestId("Avatar"); - + // Check if the avatar contains the appropriate icon, in this case CheckCircleIcon // Since CheckCircleIcon is rendered as part of the Avatar, we can check for its presence by looking for SVGs or icon classes const icon = avatar.querySelector("svg"); diff --git a/frontend/taipy-gui/src/components/Taipy/Status.tsx b/frontend/taipy-gui/src/components/Taipy/Status.tsx index b9985c46f..2f15fd7a2 100644 --- a/frontend/taipy-gui/src/components/Taipy/Status.tsx +++ b/frontend/taipy-gui/src/components/Taipy/Status.tsx @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -import React, { MouseEvent, ReactNode, useEffect, useMemo, useRef} from "react"; +import React, { MouseEvent, ReactNode, useEffect, useMemo, useRef } from "react"; import Chip from "@mui/material/Chip"; import Avatar from "@mui/material/Avatar"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; @@ -31,9 +31,8 @@ export interface StatusType { interface StatusProps extends TaipyBaseProps { value: StatusType; onClose?: (evt: MouseEvent) => void; - icon?: ReactNode; - withIcons?: boolean; - content?: string; + openedIcon?: ReactNode; + icon ?: boolean | string; } const status2Color = (status: string): "error" | "info" | "success" | "warning" => { @@ -51,111 +50,92 @@ const status2Color = (status: string): "error" | "info" | "success" | "warning" }; // Function to get the appropriate icon based on the status -const GetStatusIcon = (status: string, withIcons?: boolean): ReactNode => { +const getStatusIcon = (status: string, icon?: boolean): ReactNode => { // Use useMemo to memoize the iconProps as well const color = status2Color(status); // Memoize the iconProps const iconProps = { - sx: { fontSize: 20, color: `${color}.main` }} - - if (withIcons) { - switch (color) { - case "success": - return ; - case "warning": - return ; - case "error": - return ; - default: - return ; - } - } else { - return getInitials(status); + sx: { fontSize: 20, color: `${color}.main` }, + }; + + if (icon) { + switch (color) { + case "success": + return ; + case "warning": + return ; + case "error": + return ; + default: + return ; } - + } else { + return getInitials(status); + } }; - const chipSx = { alignSelf: "flex-start" }; const defaultAvatarStyle = { - width: '100%', - height: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }; + width: "100%", + height: "100%", + display: "flex", + alignItems: "center", + justifyContent: "center", +}; const defaultAvatarSx = { - bgcolor: 'transparent' + bgcolor: "transparent", }; const baseStyles = { - fontSize: '1rem', - textShadow: '1px 1px 4px black, -1px -1px 4px black', + fontSize: "1rem", + textShadow: "1px 1px 4px black, -1px -1px 4px black", }; -const isSvgUrl = (content?: string) => { - return content?.substring(content?.length - 4).toLowerCase() === ".svg"; // Check if it ends with ".svg" -}; +const isSvgUrl = (icon: boolean | string) => + typeof icon === "string" && icon.substring(icon?.length - 4).toLowerCase() === ".svg"; // Check if it ends with ".svg" -const isInlineSvg = (content?: string) => { -return content?.substring(0, 4).toLowerCase() === " + typeof icon === "string" && icon.substring(0, 4).toLowerCase() === " { - const { value, id } = props; - const content = props.content || undefined; - const withIcons = props.withIcons; + const { value, id, icon = false } = props; const svgRef = useRef(null); const className = useClassNames(props.libClassName, props.dynamicClassName, props.className); useEffect(() => { - if (content && svgRef.current) { - svgRef.current.innerHTML = content; + if (typeof icon === "string" && svgRef.current) { + svgRef.current.innerHTML = icon; } - }, [content]); - + }, [icon]); const chipProps = useMemo(() => { const cp: Record = {}; const statusColor = status2Color(value.status); cp.color = statusColor; - - if (isSvgUrl(content)) { - cp.avatar = ( - - ); - } - - else if(content && isInlineSvg(content)){ + + if (isSvgUrl(icon)) { + cp.avatar = ; + } else if (isInlineSvg(icon)) { cp.avatar = ( - -
+ +
); - } - - else { + } else { + const isIcon = typeof icon === "boolean" && icon; cp.avatar = ( - {GetStatusIcon(value.status, withIcons)} + {getStatusIcon(value.status, isIcon)} ); } @@ -163,11 +143,11 @@ const Status = (props: StatusProps) => { if (props.onClose) { cp.onDelete = props.onClose; } - if (props.icon) { - cp.deleteIcon = props.icon; + if (props.openedIcon) { + cp.deleteIcon = props.openedIcon; } return cp; - }, [value.status, props.onClose, props.icon, withIcons, content]); + }, [value.status, props.onClose, props.openedIcon, icon]); return ; }; diff --git a/frontend/taipy-gui/src/components/Taipy/StatusList.spec.tsx b/frontend/taipy-gui/src/components/Taipy/StatusList.spec.tsx index b2451198a..95537e41d 100644 --- a/frontend/taipy-gui/src/components/Taipy/StatusList.spec.tsx +++ b/frontend/taipy-gui/src/components/Taipy/StatusList.spec.tsx @@ -69,30 +69,30 @@ describe("StatusList Component", () => { await userEvent.click(icons[0]); expect(queryAllByTestId("CancelIcon")).toHaveLength(3); }); - it("should return 0 for unknown status", () => { - expect(getStatusIntValue("unknown")).toBe(0); - expect(getStatusIntValue("")).toBe(0); - expect(getStatusIntValue("a")).toBe(0); - expect(getStatusIntValue("z")).toBe(0); + it("should return -1 for unknown status", () => { + expect(getStatusIntValue("unknown")).toBe(-1); + expect(getStatusIntValue("")).toBe(-1); + expect(getStatusIntValue("a")).toBe(-1); + expect(getStatusIntValue("z")).toBe(-1); }); - it('should return "info" for status 1', () => { - expect(getStatusStrValue(1)).toBe("info"); + it('should return "info" for status 0', () => { + expect(getStatusStrValue(0)).toBe("info"); }); - it('should return "success" for status 2', () => { - expect(getStatusStrValue(2)).toBe("success"); + it('should return "success" for status 1', () => { + expect(getStatusStrValue(1)).toBe("success"); }); - it('should return "warning" for status 3', () => { - expect(getStatusStrValue(3)).toBe("warning"); + it('should return "warning" for status 2', () => { + expect(getStatusStrValue(2)).toBe("warning"); }); - it('should return "error" for status 4', () => { - expect(getStatusStrValue(4)).toBe("error"); + it('should return "error" for status 3', () => { + expect(getStatusStrValue(3)).toBe("error"); }); it('should return "unknown" for any other status', () => { - expect(getStatusStrValue(0)).toBe("unknown"); + expect(getStatusStrValue(0)).toBe("info"); expect(getStatusStrValue(5)).toBe("unknown"); expect(getStatusStrValue(-1)).toBe("unknown"); }); @@ -110,7 +110,7 @@ describe("StatusList Component", () => { { status: "info", message: "Information" }, { status: "warning", message: "Warning" }, ]; - + const { getByText } = render(); getByText("W"); }); @@ -119,15 +119,15 @@ describe("StatusList Component", () => { { status: "info", message: "Information" }, { status: "warning", message: "Warning" }, ]; - + const content = "" - - const { container } = render(); - + const { container } = render(); + + // Check if the SVG is rendered for the warning status const svg = container.querySelector("svg"); expect(svg).toBeInTheDocument(); }); - + }); diff --git a/frontend/taipy-gui/src/components/Taipy/StatusList.tsx b/frontend/taipy-gui/src/components/Taipy/StatusList.tsx index 37227a3f4..3efdd8cd9 100644 --- a/frontend/taipy-gui/src/components/Taipy/StatusList.tsx +++ b/frontend/taipy-gui/src/components/Taipy/StatusList.tsx @@ -26,26 +26,26 @@ import { getComponentClassName } from "./TaipyStyle"; export const getStatusIntValue = (status: string) => { status = status.toLowerCase(); if (status.startsWith("i")) { - return 1; + return 0; } else if (status.startsWith("s")) { - return 2; + return 1; } else if (status.startsWith("w")) { - return 3; + return 2; } else if (status.startsWith("e")) { - return 4; + return 3; } - return 0; + return -1; }; export const getStatusStrValue = (status: number) => { switch (status) { - case 1: + case 0: return "info"; - case 2: + case 1: return "success"; - case 3: + case 2: return "warning"; - case 4: + case 3: return "error"; default: return "unknown"; @@ -73,6 +73,8 @@ const getGlobalStatus = (values: StatusDel[]) => { const statusEqual = (v1: StatusDel, v2: StatusDel) => v1.status === v2.status && v1.message === v2.message; +const getIcon = (icons: Array, index: number) => index >= 0 && index < icons.length ? icons[index] : false; + const ORIGIN = { vertical: "bottom", horizontal: "left", @@ -87,8 +89,7 @@ interface StatusListProps extends TaipyBaseProps, TaipyHoverProps { value: Array<[string, string] | StatusType> | [string, string] | StatusType; defaultValue?: string; withoutClose?: boolean; - withIcons?: boolean; - customIcon?: string; + useIcon?: boolean | string; } const StatusList = (props: StatusListProps) => { @@ -97,19 +98,31 @@ const StatusList = (props: StatusListProps) => { const [opened, setOpened] = useState(false); const [multiple, setMultiple] = useState(false); const [anchorEl, setAnchorEl] = useState(null); - const content = useMemo(() => { - if (typeof props.customIcon === 'string') { + + const className = useClassNames(props.libClassName, props.dynamicClassName, props.className); + const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined); + + const icons = useMemo(() => { + if (typeof props.useIcon === "string") { try { - return props.customIcon.split(';'); + const iconsDict = JSON.parse(props.useIcon); + const defaultVal = iconsDict.__default !== undefined ? iconsDict.__default : false; + const res = [defaultVal, defaultVal, defaultVal, defaultVal]; + Object.entries(iconsDict).forEach(([k, v]) => { + const idx = getStatusIntValue(k); + if (idx >=0) { + res[idx] = v; + } + }); + return res; } catch (e) { - console.info(`Error parsing custom icons\n${(e as Error).message || e}`); + console.info(`Error parsing icons\n${(e as Error).message || e}`); } + return [false, false, false, false]; } - return []; - }, [props.customIcon]); + return [!!props.useIcon, !!props.useIcon, !!props.useIcon, !!props.useIcon]; - const className = useClassNames(props.libClassName, props.dynamicClassName, props.className); - const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined); + }, [props.useIcon]); useEffect(() => { let val; @@ -141,8 +154,8 @@ const StatusList = (props: StatusListProps) => { }, [value, defaultValue]); const onClose = useCallback((val: StatusDel) => { - setValues((vals) => { - const res = vals.map((v) => { + setValues((values) => { + const res = values.map((v) => { if (!v.hidden && statusEqual(v, val)) { v.hidden = !v.hidden; } @@ -164,15 +177,22 @@ const StatusList = (props: StatusListProps) => { }, []); const globalProps = useMemo( - () => (multiple ? { onClose: onOpen, icon: opened ? : } : {}), + () => (multiple ? { onClose: onOpen, openedIcon: opened ? : } : {}), [multiple, opened, onOpen] ); + const globStatus = getGlobalStatus(values); return ( <> - + {values @@ -186,8 +206,7 @@ const StatusList = (props: StatusListProps) => { value={val} className={`${className} ${getComponentClassName(props.children)}`} {...closeProp} - withIcons={props.withIcons} - content={content[idx+1] || content[0] || ''} + icon={getIcon(icons, getStatusIntValue(val.status))} /> ); })} diff --git a/taipy/gui/_renderers/builder.py b/taipy/gui/_renderers/builder.py index d359b1f79..2c101e670 100644 --- a/taipy/gui/_renderers/builder.py +++ b/taipy/gui/_renderers/builder.py @@ -77,7 +77,7 @@ class _Builder: "lov", "row_class_name", "cell_class_name", - "format_fn" + "format_fn", } def __init__( @@ -299,7 +299,7 @@ def set_number_attribute(self, name: str, default_value: t.Optional[str] = None, except ValueError: raise ValueError(f"Property {name} expects a number for control {self.__control_type}") from None elif isinstance(value, numbers.Number): - val = value # type: ignore[assignment] + val = value # type: ignore[assignment] else: raise ValueError( f"Property {name} expects a number for control {self.__control_type}, received {type(value)}" @@ -447,7 +447,7 @@ def _get_lov_adapter( # noqa: C901 else: self.__gui._add_type_for_var(value_name, t.cast(str, var_type)) if adapter is not None: - self.__gui._add_adapter_for_type(var_type, adapter) # type: ignore[arg-type] + self.__gui._add_adapter_for_type(var_type, adapter) # type: ignore[arg-type] if default_lov is not None and lov: for elt in lov: @@ -1001,6 +1001,24 @@ def __set_html_content(self, name: str, property_name: str, property_type: Prope ) return self.__set_react_attribute(_to_camel_case(property_name), _get_client_var_name(front_var)) + def _set_indexed_icons(self, name="use_icon"): + global_icon = self.__attributes.get(name) + indexed = self.get_name_indexed_property(name) + global_bool = _is_true(global_icon) if global_icon is not None and _is_boolean(global_icon) else None + if global_icon is not None and not indexed: + if global_bool is not None: + self.set_boolean_attribute(name, global_bool) + else: + self.__set_json_attribute(_to_camel_case(name), {"__default": str(global_icon)}) + elif indexed: + icons = {} + if global_icon is not None: + icons["__default"] = global_bool if global_bool is not None else str(global_icon) + for k, v in indexed.items(): + icons[k] = _is_true(v) if _is_boolean(v) else str(v) + self.__set_json_attribute(_to_camel_case(name), icons) + return self + def set_attributes(self, attributes: t.List[tuple]): # noqa: C901 """ TODO-undocumented diff --git a/taipy/gui/_renderers/factory.py b/taipy/gui/_renderers/factory.py index 14e972440..6a46db2c6 100644 --- a/taipy/gui/_renderers/factory.py +++ b/taipy/gui/_renderers/factory.py @@ -545,11 +545,9 @@ class _Factory: .set_attributes( [ ("without_close", PropertyType.boolean, False), - ("with_icons", PropertyType.boolean, False), ("hover_text", PropertyType.dynamic_string), - ("custom_icon", PropertyType.string), ] - ), + )._set_indexed_icons(), "table": lambda gui, control_type, attrs: _Builder( gui=gui, control_type=control_type, diff --git a/taipy/gui/viselements.json b/taipy/gui/viselements.json index 0df84310f..01603f3be 100644 --- a/taipy/gui/viselements.json +++ b/taipy/gui/viselements.json @@ -1687,6 +1687,12 @@ "type": "bool", "default_value": "False", "doc": "If True, the user cannot remove the status items from the list." + }, + { + "name": "use_icon", + "type": "indexed(Union[bool,str])", + "default_value": "False", + "doc": "TODO If True, status shows a predefined icon. A string shows as svg content (URL or element). Key from \"info\", \"success\", \"warning\", \"error\"." } ] }