From daa272b4fc5578ff14e6ed6dca014e3f15b83e48 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 30 Mar 2020 15:04:00 +0200 Subject: [PATCH] expand menu if SubNavigation is opened, even if it is inside an ExtensionPoint --- .../SecondaryNavigation.stories.tsx | 103 ++++++++++++++++++ .../src/navigation/SecondaryNavigation.tsx | 29 ++++- .../navigation/SecondaryNavigationItem.tsx | 42 ++----- scm-ui/ui-components/tsconfig.json | 8 +- 4 files changed, 144 insertions(+), 38 deletions(-) create mode 100644 scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx diff --git a/scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx b/scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx new file mode 100644 index 0000000000..f96909a8af --- /dev/null +++ b/scm-ui/ui-components/src/navigation/SecondaryNavigation.stories.tsx @@ -0,0 +1,103 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { storiesOf } from "@storybook/react"; +import React, { ReactElement } from "react"; +import SecondaryNavigation from "./SecondaryNavigation"; +import SecondaryNavigationItem from "./SecondaryNavigationItem"; +import styled from "styled-components"; +import SubNavigation from "./SubNavigation"; +import { Binder, ExtensionPoint, BinderContext } from "@scm-manager/ui-extensions"; +import { MemoryRouter } from "react-router-dom"; + +const Columns = styled.div` + margin: 2rem; +`; + +const starships = ( + + + + +); + +const withRoute = (route: string) => { + return (story: ReactElement) => {story}; +}; + +storiesOf("Navigation|Secondary", module) + .addDecorator(story => ( + +
{story()}
+
+ )) + .add("Default", () => + withRoute("/")( + {}}> + + + + ) + ) + .add("Collapsed", () => + withRoute("/")( + {}}> + + + + ) + ) + .add("Sub Navigation", () => + withRoute("/")( + {}}> + + {starships} + + ) + ) + .add("Sub Navigation Collapsed", () => + withRoute("/hitchhiker/starships/heart-of-gold")( + {}}> + + {starships} + + ) + ) + .add("Collapsed EP Sub", () => { + const binder = new Binder("menu"); + binder.bind("subnav.sample", starships); + return withRoute("/hitchhiker/starships/titanic")( + + {}}> + + + + + ); + }); diff --git a/scm-ui/ui-components/src/navigation/SecondaryNavigation.tsx b/scm-ui/ui-components/src/navigation/SecondaryNavigation.tsx index fd8e1dd3eb..b2465630a6 100644 --- a/scm-ui/ui-components/src/navigation/SecondaryNavigation.tsx +++ b/scm-ui/ui-components/src/navigation/SecondaryNavigation.tsx @@ -26,6 +26,7 @@ import styled from "styled-components"; import SubNavigation from "./SubNavigation"; import { matchPath, useLocation } from "react-router-dom"; import { isMenuCollapsed, MenuContext } from "./MenuContext"; +import { ExtensionPoint, Binder, useBinder } from "@scm-manager/ui-extensions"; type Props = { label: string; @@ -62,9 +63,10 @@ const MenuLabel = styled.p` const SecondaryNavigation: FC = ({ label, children, collapsed, onCollapse }) => { const location = useLocation(); + const binder = useBinder(); const menuContext = useContext(MenuContext); - const subNavActive = isSubNavigationActive(children, location.pathname); + const subNavActive = isSubNavigationActive(binder, children, location.pathname); const isCollapsed = collapsed && !subNavActive; useEffect(() => { @@ -74,7 +76,16 @@ const SecondaryNavigation: FC = ({ label, children, collapsed, onCollapse }, [subNavActive]); const childrenWithProps = React.Children.map(children, (child: ReactElement) => - React.cloneElement(child, { collapsed: isCollapsed }) + React.cloneElement(child, { + collapsed: isCollapsed, + propTransformer: (props: object) => { + const np = { + ...props, + collapsed: isCollapsed + }; + return np; + } + }) ); const arrowIcon = isCollapsed ? : ; @@ -105,16 +116,22 @@ const createParentPath = (to: string) => { return parents.join("/"); }; -const isSubNavigationActive = (children: ReactNode, url: string): boolean => { +const isSubNavigationActive = (binder: Binder, children: ReactNode, url: string): boolean => { const childArray = React.Children.toArray(children); const match = childArray - .filter(child => { - // what about extension points? + .filter(React.isValidElement) + .flatMap(child => { // @ts-ignore + if (child.type.name === ExtensionPoint.name) { + // @ts-ignore + return binder.getExtensions(child.props.name, child.props.props); + } + return [child]; + }) + .filter(child => { return child.type.name === SubNavigation.name; }) .map(child => { - // @ts-ignore return child.props; }) .find(props => { diff --git a/scm-ui/ui-components/src/navigation/SecondaryNavigationItem.tsx b/scm-ui/ui-components/src/navigation/SecondaryNavigationItem.tsx index e35acdb386..6f846e426c 100644 --- a/scm-ui/ui-components/src/navigation/SecondaryNavigationItem.tsx +++ b/scm-ui/ui-components/src/navigation/SecondaryNavigationItem.tsx @@ -21,48 +21,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, { ReactElement, ReactNode } from "react"; +import React, { FC, ReactElement, ReactNode } from "react"; import { MenuContext } from "./MenuContext"; import SubNavigation from "./SubNavigation"; import NavLink from "./NavLink"; type Props = { to: string; - icon: string; + icon?: string; label: string; title: string; + collapsed?: boolean; activeWhenMatch?: (route: any) => boolean; activeOnlyWhenExact?: boolean; children?: ReactElement[]; }; -export default class SecondaryNavigationItem extends React.Component { - render() { - const { to, icon, label, title, activeWhenMatch, activeOnlyWhenExact, children } = this.props; - if (children) { - return ( - - {({ menuCollapsed }) => ( - - {children} - - )} - - ); - } else { - return ( - - {({ menuCollapsed }) => } - - ); - } +const SecondaryNavigationItem: FC = ({ children, ...props }) => { + if (children) { + return {children}; + } else { + return ; } -} +}; + +export default SecondaryNavigationItem; diff --git a/scm-ui/ui-components/tsconfig.json b/scm-ui/ui-components/tsconfig.json index 7e3ee63a2d..450dc158b8 100644 --- a/scm-ui/ui-components/tsconfig.json +++ b/scm-ui/ui-components/tsconfig.json @@ -1,3 +1,9 @@ { - "extends": "@scm-manager/tsconfig" + "extends": "@scm-manager/tsconfig", + "compilerOptions": { + "lib": [ + "es2019", + "dom" + ] + } }