From 3b7a26a81c54309813ea5be0c05317b1750f04ae Mon Sep 17 00:00:00 2001 From: sagar davara Date: Mon, 17 Mar 2025 22:45:30 +0530 Subject: [PATCH 1/4] fix: added support for nested items in menu --- .changeset/nice-bottles-bow.md | 6 + .../src/ensemble/screens/menu.yaml | 11 +- packages/runtime/src/runtime/menu.tsx | 185 +++++++++++++----- 3 files changed, 152 insertions(+), 50 deletions(-) create mode 100644 .changeset/nice-bottles-bow.md diff --git a/.changeset/nice-bottles-bow.md b/.changeset/nice-bottles-bow.md new file mode 100644 index 000000000..339072661 --- /dev/null +++ b/.changeset/nice-bottles-bow.md @@ -0,0 +1,6 @@ +--- +"@ensembleui/react-kitchen-sink": patch +"@ensembleui/react-runtime": patch +--- + +Added support for nested menu items in menu diff --git a/apps/kitchen-sink/src/ensemble/screens/menu.yaml b/apps/kitchen-sink/src/ensemble/screens/menu.yaml index 1bdc42e50..09e1c41ee 100644 --- a/apps/kitchen-sink/src/ensemble/screens/menu.yaml +++ b/apps/kitchen-sink/src/ensemble/screens/menu.yaml @@ -41,7 +41,13 @@ ViewGroup: page: layouts - label: Actions icon: CodeOutlined - page: actions + childrens: + - label: Actions example + icon: CodeOutlined + page: actions + - label: Test Actions + icon: WidgetsOutlined + page: test_actions - label: Forms icon: CodeOutlined page: forms @@ -74,9 +80,6 @@ ViewGroup: - label: Hidden Page icon: HelpOutlineOutlined visible: false - - label: Test Actions - icon: WidgetsOutlined - page: test_actions - customItem: widget: Button: diff --git a/packages/runtime/src/runtime/menu.tsx b/packages/runtime/src/runtime/menu.tsx index d3348cb11..ee684b7d6 100644 --- a/packages/runtime/src/runtime/menu.tsx +++ b/packages/runtime/src/runtime/menu.tsx @@ -59,6 +59,7 @@ interface MenuItemProps { hasNotifications?: boolean; openNewTab?: boolean; visible?: boolean; + childrens?: MenuItemProps[]; customItem?: { widget?: { [key: string]: unknown }; selectedWidget?: { [key: string]: unknown }; @@ -165,12 +166,13 @@ export const EnsembleMenu: React.FC<{ const [isCollapsed, setIsCollapsed] = useState( type === EnsembleMenuModelType.Drawer, ); - + const [defaultOpenKeys, setDefaultOpenKeys] = useState(); const outletContext = { isMenuCollapsed: isCollapsed, setMenuCollapsed: setIsCollapsed, }; const { id, items: rawItems, styles, header, footer, onCollapse } = menu; + // custom items may contain their own bindings to be evaluated in dynamic context const itemInputs = rawItems?.map((item) => omit(item, "customItem"), @@ -204,13 +206,29 @@ export const EnsembleMenu: React.FC<{ }, [items]); useEffect(() => { - const locationMatch = items?.find( - (item) => + let itemCount = 0; + for (const item of items || []) { + if ( item.page && - `/${item.page.toLowerCase()}` === location.pathname.toLowerCase(), - ); - if (locationMatch) { - setSelectedItem(locationMatch.page); + `/${item.page.toLowerCase()}` === location.pathname.toLowerCase() + ) { + setSelectedItem(item.page); + } + + if (item.childrens && item.childrens.length > 0) { + for (const childItem of item.childrens) { + if ( + childItem.page && + `/${childItem.page.toLowerCase()}` === + location.pathname.toLowerCase() + ) { + setSelectedItem(childItem.page); + setDefaultOpenKeys(`submenu-${itemCount}`); + } + } + } + + itemCount++; } }, [location.pathname, items]); @@ -223,10 +241,12 @@ export const EnsembleMenu: React.FC<{ const onCollapseCallback = useCallback(() => { return onCollapseAction?.callback(); }, [onCollapseAction?.callback]); + return (
{type === EnsembleMenuModelType.SideBar ? ( ) : ( ; isCollapsed: boolean; selectedItem: string | undefined; + defaultOpenKeys: string | undefined; setSelectedItem: (s: string) => void; width?: string; -}> = ({ values, isCollapsed, selectedItem, setSelectedItem }) => { +}> = ({ + values, + isCollapsed, + selectedItem, + defaultOpenKeys, + setSelectedItem, +}) => { return ( {values?.header ? EnsembleRuntime.render([values.header]) : null} ; handleClose: () => void; isOpen: boolean; selectedItem: string | undefined; setSelectedItem: (s: string) => void; -}> = ({ values, handleClose, isOpen, selectedItem, setSelectedItem }) => { +}> = ({ + defaultOpenKeys, + values, + handleClose, + isOpen, + selectedItem, + setSelectedItem, +}) => { const validPosition = ["left", "right", "top", "bottom"]; return ( @@ -335,6 +372,7 @@ export const DrawerMenu: React.FC<{ > {values?.header ? EnsembleRuntime.render([values.header]) : null} void; isCollapsed?: boolean; }> = ({ items, styles, selectedItem, + defaultOpenKeys, setSelectedItem, isCollapsed = false, }) => { @@ -410,6 +450,7 @@ const MenuItems: React.FC<{ > - {items.map((item, itemIndex) => ( - { - if (!item.openNewTab && item.page) { - setSelectedItem(item.page); - } - }} - style={{ - color: - selectedItem === item.page - ? (styles.selectedColor as string) ?? "white" - : (styles.labelColor as string) ?? "grey", - display: item.visible === false ? "none" : "flex", - justifyContent: "center", - borderRadius: 0, - alignItems: "center", - fontSize: - selectedItem === item.page - ? `${ - parseInt( - `${styles.labelFontSize ? styles.labelFontSize : 1}` || - "1", - ) + 0.2 - }rem` - : `${styles.labelFontSize ? styles.labelFontSize : 1}rem`, - height: "auto", - ...(selectedItem === item.page - ? styles.onSelectStyles ?? {} - : {}), - }} - > - {getLabel(item)} - - ))} + {items.map((item, itemIndex) => + item.childrens ? ( + + {item.childrens.map((childItem, childIndex) => ( + { + if (!childItem.openNewTab && childItem.page) { + setSelectedItem(childItem.page); + } + }} + style={{ + color: + selectedItem === childItem.page + ? (styles.selectedColor as string) ?? "white" + : (styles.labelColor as string) ?? "grey", + display: childItem.visible === false ? "none" : "flex", + justifyContent: "center", + borderRadius: 0, + alignItems: "center", + fontSize: + selectedItem === childItem.page + ? `${ + parseInt( + `${styles.labelFontSize ? styles.labelFontSize : 1}` || + "1", + ) + 0.2 + }rem` + : `${styles.labelFontSize ? styles.labelFontSize : 1}rem`, + height: "auto", + ...(selectedItem === childItem.page + ? styles.onSelectStyles ?? {} + : {}), + }} + > + + {getLabel(childItem)} + + + ))} + + ) : ( + { + if (!item.openNewTab && item.page) { + setSelectedItem(item.page); + } + }} + style={{ + color: + selectedItem === item.page + ? (styles.selectedColor as string) ?? "white" + : (styles.labelColor as string) ?? "grey", + display: item.visible === false ? "none" : "flex", + justifyContent: "center", + borderRadius: 0, + alignItems: "center", + fontSize: + selectedItem === item.page + ? `${ + parseInt( + `${styles.labelFontSize ? styles.labelFontSize : 1}` || + "1", + ) + 0.2 + }rem` + : `${styles.labelFontSize ? styles.labelFontSize : 1}rem`, + height: "auto", + ...(selectedItem === item.page + ? styles.onSelectStyles ?? {} + : {}), + }} + > + {getLabel(item)} + + ), + )} ); From a7845a07569c99be98456f5814848a34719b72e7 Mon Sep 17 00:00:00 2001 From: sagar davara Date: Tue, 18 Mar 2025 12:06:45 +0530 Subject: [PATCH 2/4] fix: improve sidemenu widget --- .../src/ensemble/screens/home.yaml | 3 -- .../src/ensemble/screens/menu.yaml | 10 +++++ packages/runtime/src/runtime/menu.tsx | 43 +++++++++++++------ 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/apps/kitchen-sink/src/ensemble/screens/home.yaml b/apps/kitchen-sink/src/ensemble/screens/home.yaml index f0d4a9c8a..606322322 100644 --- a/apps/kitchen-sink/src/ensemble/screens/home.yaml +++ b/apps/kitchen-sink/src/ensemble/screens/home.yaml @@ -12,9 +12,6 @@ View: console.log('>>> secret variable >>>', ensemble.secrets.dummyOauthSecret) ensemble.storage.set('products', []); ensemble.invokeAPI('getDummyProducts').then((res) => ensemble.storage.set('products', (res?.body?.users || []).map((i) => ({ ...i, name: i.firstName + ' ' + i.lastName })))); - const res = await ensemble.invokeAPI('getDummyNumbers') - await new Promise((resolve) => setTimeout(resolve, 5000)) - return res onComplete: executeCode: | console.log('API triggered', result) diff --git a/apps/kitchen-sink/src/ensemble/screens/menu.yaml b/apps/kitchen-sink/src/ensemble/screens/menu.yaml index 09e1c41ee..2666cbb79 100644 --- a/apps/kitchen-sink/src/ensemble/screens/menu.yaml +++ b/apps/kitchen-sink/src/ensemble/screens/menu.yaml @@ -80,6 +80,16 @@ ViewGroup: - label: Hidden Page icon: HelpOutlineOutlined visible: false + - label: Actions + icon: CodeOutlined + expanded: true + childrens: + - label: Actions example + icon: CodeOutlined + page: actions1 + - label: Test Actions + icon: WidgetsOutlined + page: test_actions1 - customItem: widget: Button: diff --git a/packages/runtime/src/runtime/menu.tsx b/packages/runtime/src/runtime/menu.tsx index ee684b7d6..3acb2f3c4 100644 --- a/packages/runtime/src/runtime/menu.tsx +++ b/packages/runtime/src/runtime/menu.tsx @@ -59,6 +59,7 @@ interface MenuItemProps { hasNotifications?: boolean; openNewTab?: boolean; visible?: boolean; + expanded?: boolean; childrens?: MenuItemProps[]; customItem?: { widget?: { [key: string]: unknown }; @@ -166,7 +167,8 @@ export const EnsembleMenu: React.FC<{ const [isCollapsed, setIsCollapsed] = useState( type === EnsembleMenuModelType.Drawer, ); - const [defaultOpenKeys, setDefaultOpenKeys] = useState(); + const [defaultOpenKeys, setDefaultOpenKeys] = useState([]); + const [isInitialized, setIsInitialized] = useState(false); const outletContext = { isMenuCollapsed: isCollapsed, setMenuCollapsed: setIsCollapsed, @@ -206,8 +208,9 @@ export const EnsembleMenu: React.FC<{ }, [items]); useEffect(() => { - let itemCount = 0; - for (const item of items || []) { + const openItems: string[] = []; + + items?.forEach((item, index) => { if ( item.page && `/${item.page.toLowerCase()}` === location.pathname.toLowerCase() @@ -216,20 +219,28 @@ export const EnsembleMenu: React.FC<{ } if (item.childrens && item.childrens.length > 0) { - for (const childItem of item.childrens) { - if ( + const hasActiveChild = item.childrens.some((childItem) => { + const isActive = childItem.page && `/${childItem.page.toLowerCase()}` === - location.pathname.toLowerCase() - ) { + location.pathname.toLowerCase(); + + if (isActive) { setSelectedItem(childItem.page); - setDefaultOpenKeys(`submenu-${itemCount}`); + return true; } + + return false; + }); + + if (hasActiveChild || item.expanded) { + openItems.push(`submenu-${index}`); } } + }); - itemCount++; - } + setDefaultOpenKeys(openItems); + setIsInitialized(true); }, [location.pathname, items]); const handleClose = (): void => { @@ -242,6 +253,10 @@ export const EnsembleMenu: React.FC<{ return onCollapseAction?.callback(); }, [onCollapseAction?.callback]); + if (!isInitialized) { + return null; + } + return (
{type === EnsembleMenuModelType.SideBar ? ( @@ -282,7 +297,7 @@ export const SideBarMenu: React.FC<{ values?: MenuBaseProps; isCollapsed: boolean; selectedItem: string | undefined; - defaultOpenKeys: string | undefined; + defaultOpenKeys: string[] | undefined; setSelectedItem: (s: string) => void; width?: string; }> = ({ @@ -324,7 +339,7 @@ export const SideBarMenu: React.FC<{ }; export const DrawerMenu: React.FC<{ - defaultOpenKeys: string | undefined; + defaultOpenKeys: string[] | undefined; values?: MenuBaseProps; handleClose: () => void; isOpen: boolean; @@ -387,7 +402,7 @@ const MenuItems: React.FC<{ items: MenuItemProps[]; styles: MenuStyles; selectedItem: string | undefined; - defaultOpenKeys: string | undefined; + defaultOpenKeys: string[] | undefined; setSelectedItem: (s: string) => void; isCollapsed?: boolean; }> = ({ @@ -449,8 +464,8 @@ const MenuItems: React.FC<{ }} > Date: Wed, 19 Mar 2025 15:31:35 +0530 Subject: [PATCH 3/4] fix: improve style management in subItems --- .../src/ensemble/screens/menu.yaml | 4 ++-- packages/runtime/src/runtime/menu.tsx | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/apps/kitchen-sink/src/ensemble/screens/menu.yaml b/apps/kitchen-sink/src/ensemble/screens/menu.yaml index 2666cbb79..ec4732416 100644 --- a/apps/kitchen-sink/src/ensemble/screens/menu.yaml +++ b/apps/kitchen-sink/src/ensemble/screens/menu.yaml @@ -41,7 +41,7 @@ ViewGroup: page: layouts - label: Actions icon: CodeOutlined - childrens: + children: - label: Actions example icon: CodeOutlined page: actions @@ -83,7 +83,7 @@ ViewGroup: - label: Actions icon: CodeOutlined expanded: true - childrens: + children: - label: Actions example icon: CodeOutlined page: actions1 diff --git a/packages/runtime/src/runtime/menu.tsx b/packages/runtime/src/runtime/menu.tsx index 3acb2f3c4..ea18053d7 100644 --- a/packages/runtime/src/runtime/menu.tsx +++ b/packages/runtime/src/runtime/menu.tsx @@ -60,7 +60,7 @@ interface MenuItemProps { openNewTab?: boolean; visible?: boolean; expanded?: boolean; - childrens?: MenuItemProps[]; + children?: MenuItemProps[]; customItem?: { widget?: { [key: string]: unknown }; selectedWidget?: { [key: string]: unknown }; @@ -218,8 +218,8 @@ export const EnsembleMenu: React.FC<{ setSelectedItem(item.page); } - if (item.childrens && item.childrens.length > 0) { - const hasActiveChild = item.childrens.some((childItem) => { + if (item.children && item.children.length > 0) { + const hasActiveChild = item.children.some((childItem) => { const isActive = childItem.page && `/${childItem.page.toLowerCase()}` === @@ -463,6 +463,14 @@ const MenuItems: React.FC<{ }, }} > + + {items.map((item, itemIndex) => - item.childrens ? ( + item.children ? ( - {item.childrens.map((childItem, childIndex) => ( + {item.children.map((childItem, childIndex) => ( Date: Fri, 21 Mar 2025 20:21:44 +0530 Subject: [PATCH 4/4] fix: code optimization --- packages/runtime/src/runtime/menu.tsx | 149 ++++++++++++++------------ 1 file changed, 78 insertions(+), 71 deletions(-) diff --git a/packages/runtime/src/runtime/menu.tsx b/packages/runtime/src/runtime/menu.tsx index ea18053d7..15a939056 100644 --- a/packages/runtime/src/runtime/menu.tsx +++ b/packages/runtime/src/runtime/menu.tsx @@ -398,6 +398,59 @@ export const DrawerMenu: React.FC<{ ); }; +const RenderMenuItem: React.FC<{ + menuItem: MenuItemProps; + styles: MenuStyles; + selectedItem: string | undefined; + setSelectedItem: (s: string) => void; + itemIndex: number; + icon: ReactNode; + label: ReactNode; +}> = ({ + menuItem, + styles, + selectedItem, + setSelectedItem, + itemIndex, + icon, + label, +}) => { + return ( + { + if (!menuItem.openNewTab && menuItem.page) { + setSelectedItem(menuItem.page); + } + }} + style={{ + color: + selectedItem === menuItem.page + ? (styles.selectedColor as string) ?? "white" + : (styles.labelColor as string) ?? "grey", + display: menuItem.visible === false ? "none" : "flex", + justifyContent: "center", + borderRadius: 0, + alignItems: "center", + fontSize: + selectedItem === menuItem.page + ? `${ + parseInt( + `${styles.labelFontSize ? styles.labelFontSize : 1}` || "1", + ) + 0.2 + }rem` + : `${styles.labelFontSize ? styles.labelFontSize : 1}rem`, + height: "auto", + ...(selectedItem === menuItem.page ? styles.onSelectStyles ?? {} : {}), + }} + > + {label} + + ); +}; + const MenuItems: React.FC<{ items: MenuItemProps[]; styles: MenuStyles; @@ -468,6 +521,14 @@ const MenuItems: React.FC<{ .ant-menu-submenu-title{ color: inherit !important } + + .ant-menu-item { + padding-left: 24px !important; + } + + .ant-menu-sub { + padding-left: 24px !important; + } `} @@ -499,85 +560,31 @@ const MenuItems: React.FC<{ title={getLabel(item)} > {item.children.map((childItem, childIndex) => ( - { - if (!childItem.openNewTab && childItem.page) { - setSelectedItem(childItem.page); - } - }} - style={{ - color: - selectedItem === childItem.page - ? (styles.selectedColor as string) ?? "white" - : (styles.labelColor as string) ?? "grey", - display: childItem.visible === false ? "none" : "flex", - justifyContent: "center", - borderRadius: 0, - alignItems: "center", - fontSize: - selectedItem === childItem.page - ? `${ - parseInt( - `${styles.labelFontSize ? styles.labelFontSize : 1}` || - "1", - ) + 0.2 - }rem` - : `${styles.labelFontSize ? styles.labelFontSize : 1}rem`, - height: "auto", - ...(selectedItem === childItem.page - ? styles.onSelectStyles ?? {} - : {}), - }} - > - - {getLabel(childItem)} - - + label={getLabel(childItem)} + menuItem={childItem} + selectedItem={selectedItem} + setSelectedItem={setSelectedItem} + styles={styles} + /> ))} ) : ( - { - if (!item.openNewTab && item.page) { - setSelectedItem(item.page); - } - }} - style={{ - color: - selectedItem === item.page - ? (styles.selectedColor as string) ?? "white" - : (styles.labelColor as string) ?? "grey", - display: item.visible === false ? "none" : "flex", - justifyContent: "center", - borderRadius: 0, - alignItems: "center", - fontSize: - selectedItem === item.page - ? `${ - parseInt( - `${styles.labelFontSize ? styles.labelFontSize : 1}` || - "1", - ) + 0.2 - }rem` - : `${styles.labelFontSize ? styles.labelFontSize : 1}rem`, - height: "auto", - ...(selectedItem === item.page - ? styles.onSelectStyles ?? {} - : {}), - }} - > - {getLabel(item)} - + label={getLabel(item)} + menuItem={item} + selectedItem={selectedItem} + setSelectedItem={setSelectedItem} + styles={styles} + /> ), )}