From ea0f921cd3008e59dd33c1fc1d370af515b3cb5c Mon Sep 17 00:00:00 2001 From: hiba-aldalaty Date: Fri, 8 Oct 2021 17:08:03 +0100 Subject: [PATCH 001/557] Implement Expedias new Scalable Side Nav design - items with submenus and expand on click Signed-off-by: hiba-aldalaty --- packages/app/src/components/Root/Root.tsx | 2 + .../src/layout/Sidebar/Bar.tsx | 130 +++++++------- .../src/layout/Sidebar/Items.tsx | 167 ++++++++++++++++- .../src/layout/Sidebar/Sidebar.stories.tsx | 40 ++++- .../src/layout/Sidebar/Submenu.tsx | 97 ++++++++++ .../src/layout/Sidebar/SubmenuItem.tsx | 170 ++++++++++++++++++ .../src/layout/Sidebar/config.ts | 33 +++- .../src/layout/Sidebar/icons/APIsIcon.tsx | 33 ++++ .../Sidebar/icons/CatalogSidebarLogo.tsx | 40 +++++ .../layout/Sidebar/icons/DoubleArrowLeft.tsx | 50 ++++++ .../layout/Sidebar/icons/DoubleArrowRight.tsx | 50 ++++++ .../src/layout/Sidebar/icons/MiscIcon.tsx | 33 ++++ .../src/layout/Sidebar/icons/ServicesIcon.tsx | 33 ++++ .../src/layout/Sidebar/index.ts | 3 +- packages/theme/src/themes.ts | 18 ++ packages/theme/src/types.ts | 9 + 16 files changed, 834 insertions(+), 74 deletions(-) create mode 100644 packages/core-components/src/layout/Sidebar/Submenu.tsx create mode 100644 packages/core-components/src/layout/Sidebar/SubmenuItem.tsx create mode 100644 packages/core-components/src/layout/Sidebar/icons/APIsIcon.tsx create mode 100644 packages/core-components/src/layout/Sidebar/icons/CatalogSidebarLogo.tsx create mode 100644 packages/core-components/src/layout/Sidebar/icons/DoubleArrowLeft.tsx create mode 100644 packages/core-components/src/layout/Sidebar/icons/DoubleArrowRight.tsx create mode 100644 packages/core-components/src/layout/Sidebar/icons/MiscIcon.tsx create mode 100644 packages/core-components/src/layout/Sidebar/icons/ServicesIcon.tsx diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 7a666a020a..b0318cdaf8 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -40,6 +40,7 @@ import { SidebarDivider, SidebarSpace, SidebarScrollWrapper, + SidebarExpandButton, } from '@backstage/core-components'; const useSidebarLogoStyles = makeStyles({ @@ -98,6 +99,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index 750ec6bbdc..ad73b52435 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -16,10 +16,17 @@ import { makeStyles, useMediaQuery } from '@material-ui/core'; import clsx from 'clsx'; -import React, { useRef, useState, useContext, PropsWithChildren } from 'react'; +import React, { + useState, + useContext, + PropsWithChildren, + useEffect, +} from 'react'; import { sidebarConfig, SidebarContext } from './config'; import { BackstageTheme } from '@backstage/theme'; import { SidebarPinStateContext } from './Page'; +import DoubleArrowRight from './icons/DoubleArrowRight'; +import DoubleArrowLeft from './icons/DoubleArrowLeft'; export type SidebarClassKey = 'root' | 'drawer' | 'drawerOpen'; @@ -45,7 +52,6 @@ const useStyles = makeStyles( msOverflowStyle: 'none', scrollbarWidth: 'none', width: sidebarConfig.drawerWidthClosed, - borderRight: `1px solid #383838`, transition: theme.transitions.create('width', { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.shortest, @@ -57,6 +63,19 @@ const useStyles = makeStyles( display: 'none', }, }, + expandButton: { + background: 'none', + border: 'none', + color: theme.palette.navigation.color, + width: '100%', + cursor: 'pointer', + position: 'relative', + height: 48, + }, + arrows: { + position: 'absolute', + right: 10, + }, drawerOpen: { width: sidebarConfig.drawerWidthOpen, transition: theme.transitions.create('width', { @@ -68,81 +87,24 @@ const useStyles = makeStyles( { name: 'BackstageSidebar' }, ); -enum State { - Closed, - Idle, - Open, -} - -type Props = { - openDelayMs?: number; - closeDelayMs?: number; -}; - -export function Sidebar(props: PropsWithChildren) { - const { - openDelayMs = sidebarConfig.defaultOpenDelayMs, - closeDelayMs = sidebarConfig.defaultCloseDelayMs, - children, - } = props; +export function Sidebar({ children }: PropsWithChildren<{}>) { const classes = useStyles(); - const isSmallScreen = useMediaQuery(theme => - theme.breakpoints.down('md'), - ); - const [state, setState] = useState(State.Closed); - const hoverTimerRef = useRef(); + const { isPinned } = useContext(SidebarPinStateContext); + const [isOpen, setIsOpen] = useState(isPinned); - const handleOpen = () => { + useEffect(() => { if (isPinned) { - return; + setIsOpen(true); } - if (hoverTimerRef.current) { - clearTimeout(hoverTimerRef.current); - hoverTimerRef.current = undefined; - } - if (state !== State.Open && !isSmallScreen) { - hoverTimerRef.current = window.setTimeout(() => { - hoverTimerRef.current = undefined; - setState(State.Open); - }, openDelayMs); - - setState(State.Idle); - } - }; - - const handleClose = () => { - if (isPinned) { - return; - } - if (hoverTimerRef.current) { - clearTimeout(hoverTimerRef.current); - hoverTimerRef.current = undefined; - } - if (state === State.Idle) { - setState(State.Closed); - } else if (state === State.Open) { - hoverTimerRef.current = window.setTimeout(() => { - hoverTimerRef.current = undefined; - setState(State.Closed); - }, closeDelayMs); - } - }; - - const isOpen = (state === State.Open && !isSmallScreen) || isPinned; + }, [isPinned]); return ( -
+
) {
); } + +export const SidebarExpandButton = () => { + const classes = useStyles(); + const isSmallScreen = useMediaQuery(theme => + theme.breakpoints.down('md'), + ); + const { isPinned } = useContext(SidebarPinStateContext); + const { isOpen, setIsOpen } = useContext(SidebarContext); + + const openDelayMs = sidebarConfig.defaultOpenDelayMs; + const closeDelayMs = sidebarConfig.defaultCloseDelayMs; + + const handleClick = () => { + if (isPinned || isSmallScreen) { + return; + } + const delayMs = isOpen ? openDelayMs : closeDelayMs; + setTimeout(async () => { + setIsOpen(!isOpen); + }, delayMs); + }; + + if (isSmallScreen || isPinned) { + return null; + } + + return ( + + ); +}; diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 8bd80b6825..e735468e95 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -25,11 +25,14 @@ import { Typography, } from '@material-ui/core'; import { CreateCSSProperties } from '@material-ui/core/styles/withStyles'; +import ArrowRightIcon from '@material-ui/icons/ArrowRight'; import SearchIcon from '@material-ui/icons/Search'; import clsx from 'clsx'; import React, { + Children, forwardRef, KeyboardEventHandler, + PropsWithChildren, ReactNode, useContext, useState, @@ -37,10 +40,16 @@ import React, { import { Link, NavLinkProps, + resolvePath, useLocation, useResolvedPath, } from 'react-router-dom'; -import { sidebarConfig, SidebarContext } from './config'; +import { + sidebarConfig, + SidebarContext, + ItemWithSubmenuContext, +} from './config'; +import { Submenu } from './Submenu'; export type SidebarItemClassKey = | 'root' @@ -89,6 +98,14 @@ const useStyles = makeStyles( open: { width: drawerWidthOpen, }, + highlightable: { + '&:hover': { + background: theme.palette.navigation.navItem.hoverBackground, // TODO: consider + }, + }, + highlighted: { + background: theme.palette.navigation.navItem.hoverBackground, // TODO: consider + }, label: { // XXX (@koroeskohr): I can't seem to achieve the desired font-weight from the designs fontWeight: 'bold', @@ -127,13 +144,24 @@ const useStyles = makeStyles( textAlign: 'center', marginRight: theme.spacing(1), }, + closedItemIcon: { + width: '100%', + justifyContent: 'center', + }, + submenuArrow: { + position: 'absolute', + right: 0, + }, selected: { '&$root': { borderLeft: `solid ${selectedIndicatorWidth}px ${theme.palette.navigation.indicator}`, color: theme.palette.navigation.selectedColor, }, '&$closed': { - width: drawerWidthClosed - selectedIndicatorWidth, + width: drawerWidthClosed, + }, + '& $closedItemIcon': { + paddingRight: selectedIndicatorWidth, }, '& $iconContainer': { marginLeft: -selectedIndicatorWidth, @@ -144,10 +172,117 @@ const useStyles = makeStyles( { name: 'BackstageSidebarItem' }, ); +type ItemWithSubmenuProps = { + label?: string; + title?: string; + hasNotifications?: boolean; + icon: IconComponent; + children: ReactNode; +}; +const ItemWithSubmenu = ({ + label, + title, + hasNotifications = false, + icon: Icon, + children, +}: PropsWithChildren) => { + const classes = useStyles(); + const [isHoveredOn, setIsHoveredOn] = useState(false); + const toPathnames: string[] = []; + + let isActive; + const { pathname: locationPathname } = useLocation(); + + // Menu item is active if any of its children have active paths + Children.forEach(children, element => { + if (!React.isValidElement(element)) return; + if (element.props.hasDropDown && element.props.dropdownItems) { + element.props.dropdownItems.map((item: { to: string }) => + toPathnames.push(item.to), + ); + } else if (element.props.to) { + toPathnames.push(element.props.to); + } + }); + toPathnames.some(to => { + const toPathname = resolvePath(to); + isActive = locationPathname === toPathname.pathname; + return isActive; + }); + + const handleMouseEnter = () => { + setIsHoveredOn(true); + }; + const handleMouseLeave = () => { + setIsHoveredOn(false); + }; + + const { isOpen } = useContext(SidebarContext); + const itemIcon = ( + + + + ); + const openContent = ( + <> +
+ {itemIcon} +
+ {label && ( + + {label} + + )} +
{}
+ + ); + const closedContent = itemIcon; + + return ( + +
+
+ {isOpen ? openContent : closedContent} + {!isHoveredOn && ( + + )} +
+ {isHoveredOn && {children}} +
+
+ ); +}; + type SidebarItemBaseProps = { icon: IconComponent; text?: string; hasNotifications?: boolean; + hasSubMenu?: boolean; + submenuTitle?: string; + disableHighlight?: boolean; children?: ReactNode; className?: string; }; @@ -222,6 +357,9 @@ export const SidebarItem = forwardRef((props, ref) => { icon: Icon, text, hasNotifications = false, + hasSubMenu = false, + submenuTitle, + disableHighlight = false, onClick, children, className, @@ -239,6 +377,7 @@ export const SidebarItem = forwardRef((props, ref) => { variant="dot" overlap="circular" invisible={!hasNotifications} + className={clsx(isOpen ? undefined : classes.closedItemIcon)} > @@ -248,7 +387,7 @@ export const SidebarItem = forwardRef((props, ref) => { const openContent = ( <> -
+
{itemIcon}
{text && ( @@ -269,9 +408,24 @@ export const SidebarItem = forwardRef((props, ref) => { classes.root, isOpen ? classes.open : classes.closed, isButtonItem(props) && classes.buttonItem, + disableHighlight ? undefined : classes.highlightable, ), }; + // If submenu prop is true, return ItemWithSubmenu + if (hasSubMenu) { + return ( + + {children} + + ); + } + if (isButtonItem(props)) { return ( + {hasDropDown && showDropDown && ( +
+ {dropdownItems.map((object, key) => ( + + {object.title} + + ))} +
+ )} +
+ ); + } + + return ( +
+ + + + {title} + + +
+ ); +}; diff --git a/packages/core-components/src/layout/Sidebar/config.ts b/packages/core-components/src/layout/Sidebar/config.ts index d2a690e38f..e7a9e26e1d 100644 --- a/packages/core-components/src/layout/Sidebar/config.ts +++ b/packages/core-components/src/layout/Sidebar/config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createContext } from 'react'; +import { createContext, Dispatch, SetStateAction } from 'react'; const drawerWidthClosed = 72; const iconPadding = 24; @@ -37,13 +37,44 @@ export const sidebarConfig = { userBadgeDiameter: drawerWidthClosed - userBadgePadding * 2, }; +export const submenuConfig = { + drawerWidthClosed: 0, + drawerWidthOpen: 202, + // As per NN/g's guidance on timing for exposing hidden content + // See https://www.nngroup.com/articles/timing-exposing-content/ + defaultOpenDelayMs: 100, + defaultCloseDelayMs: 0, + defaultFadeDuration: 200, + logoHeight: 32, + iconContainerWidth: drawerWidthClosed, + iconSize: drawerWidthClosed - iconPadding * 2, + iconPadding, + selectedIndicatorWidth: 3, + userBadgePadding, + userBadgeDiameter: drawerWidthClosed - userBadgePadding * 2, +}; + export const SIDEBAR_INTRO_LOCAL_STORAGE = '@backstage/core/sidebar-intro-dismissed'; export type SidebarContextType = { isOpen: boolean; + setIsOpen: Dispatch>; }; export const SidebarContext = createContext({ isOpen: false, + setIsOpen: () => {}, }); + +export type ItemWithSubmenuContextType = { + isHoveredOn: boolean; + setIsHoveredOn: Dispatch>; +}; + +export const ItemWithSubmenuContext = createContext( + { + isHoveredOn: false, + setIsHoveredOn: () => {}, + }, +); diff --git a/packages/core-components/src/layout/Sidebar/icons/APIsIcon.tsx b/packages/core-components/src/layout/Sidebar/icons/APIsIcon.tsx new file mode 100644 index 0000000000..a185ab346d --- /dev/null +++ b/packages/core-components/src/layout/Sidebar/icons/APIsIcon.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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'; + +export const APIsIcon = () => { + return ( + + + + ); +}; diff --git a/packages/core-components/src/layout/Sidebar/icons/CatalogSidebarLogo.tsx b/packages/core-components/src/layout/Sidebar/icons/CatalogSidebarLogo.tsx new file mode 100644 index 0000000000..d42a3ea957 --- /dev/null +++ b/packages/core-components/src/layout/Sidebar/icons/CatalogSidebarLogo.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { SvgIcon } from '@material-ui/core'; +import React from 'react'; + +export const CatalogSidebarLogo = () => { + return ( + + + + + ); +}; diff --git a/packages/core-components/src/layout/Sidebar/icons/DoubleArrowLeft.tsx b/packages/core-components/src/layout/Sidebar/icons/DoubleArrowLeft.tsx new file mode 100644 index 0000000000..ec1dbcdd31 --- /dev/null +++ b/packages/core-components/src/layout/Sidebar/icons/DoubleArrowLeft.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ + svg: { + width: 'auto', + height: 15, + }, + path: { + fill: '#7df3e1', + }, +}); +const DoubleArrowRight = () => { + const classes = useStyles(); + + return ( + + + + + ); +}; + +export default DoubleArrowRight; diff --git a/packages/core-components/src/layout/Sidebar/icons/DoubleArrowRight.tsx b/packages/core-components/src/layout/Sidebar/icons/DoubleArrowRight.tsx new file mode 100644 index 0000000000..00d595175d --- /dev/null +++ b/packages/core-components/src/layout/Sidebar/icons/DoubleArrowRight.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ + svg: { + width: 'auto', + height: 15, + }, + path: { + fill: '#7df3e1', + }, +}); +const DoubleArrowRight = () => { + const classes = useStyles(); + + return ( + + + + + ); +}; + +export default DoubleArrowRight; diff --git a/packages/core-components/src/layout/Sidebar/icons/MiscIcon.tsx b/packages/core-components/src/layout/Sidebar/icons/MiscIcon.tsx new file mode 100644 index 0000000000..67010baeb5 --- /dev/null +++ b/packages/core-components/src/layout/Sidebar/icons/MiscIcon.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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'; + +export const MiscIcon = () => { + return ( + + + + ); +}; diff --git a/packages/core-components/src/layout/Sidebar/icons/ServicesIcon.tsx b/packages/core-components/src/layout/Sidebar/icons/ServicesIcon.tsx new file mode 100644 index 0000000000..49a619d422 --- /dev/null +++ b/packages/core-components/src/layout/Sidebar/icons/ServicesIcon.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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'; + +export const ServicesIcon = () => { + return ( + + + + ); +}; diff --git a/packages/core-components/src/layout/Sidebar/index.ts b/packages/core-components/src/layout/Sidebar/index.ts index 067a9bf2eb..f25f7355f7 100644 --- a/packages/core-components/src/layout/Sidebar/index.ts +++ b/packages/core-components/src/layout/Sidebar/index.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -export { Sidebar } from './Bar'; +export { Sidebar, SidebarExpandButton } from './Bar'; +export { SubmenuItem } from './SubmenuItem'; export type { SidebarClassKey } from './Bar'; export { SidebarPage, SidebarPinStateContext } from './Page'; export type { SidebarPinStateContextType, SidebarPageClassKey } from './Page'; diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts index d7a4a6d785..da5925b237 100644 --- a/packages/theme/src/themes.ts +++ b/packages/theme/src/themes.ts @@ -70,6 +70,15 @@ export const lightTheme = createTheme({ indicator: '#9BF0E1', color: '#b5b5b5', selectedColor: '#FFF', + navItem: { + hoverBackground: '#404040', + }, + submenu: { + background: '#404040', + indicator: '#9BF0E1', + color: '#b5b5b5', + selectedColor: '#FFF', + }, }, pinSidebarButton: { icon: '#181818', @@ -139,6 +148,15 @@ export const darkTheme = createTheme({ indicator: '#9BF0E1', color: '#b5b5b5', selectedColor: '#FFF', + navItem: { + hoverBackground: '#404040', + }, + submenu: { + background: '#404040', + indicator: '#9BF0E1', + color: '#b5b5b5', + selectedColor: '#FFF', + }, }, pinSidebarButton: { icon: '#404040', diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index 76a1b7eb5c..0b1217f00f 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -48,6 +48,15 @@ type PaletteAdditions = { indicator: string; color: string; selectedColor: string; + navItem: { + hoverBackground: string; + }; + submenu: { + background: string; + indicator: string; + color: string; + selectedColor: string; + }; }; tabbar: { indicator: string; From a35b6459d50b3979509365c532a51932f7c53ca9 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 21 Oct 2021 21:53:51 +0100 Subject: [PATCH 002/557] feat: Created `AzurePullRequestsIcon` component. Signed-off-by: Marley Powell --- .../AzurePullRequestsIcon.tsx | 28 +++++++++++++++++++ .../components/AzurePullRequestsIcon/index.ts | 17 +++++++++++ 2 files changed, 45 insertions(+) create mode 100644 plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx create mode 100644 plugins/azure-devops/src/components/AzurePullRequestsIcon/index.ts diff --git a/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx b/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx new file mode 100644 index 0000000000..3159556788 --- /dev/null +++ b/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx @@ -0,0 +1,28 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { SvgIcon, SvgIconProps } from '@material-ui/core'; + +import React from 'react'; + +export const AzurePullRequestsIcon = (props: SvgIconProps) => ( + + + +); diff --git a/plugins/azure-devops/src/components/AzurePullRequestsIcon/index.ts b/plugins/azure-devops/src/components/AzurePullRequestsIcon/index.ts new file mode 100644 index 0000000000..1f49119b4b --- /dev/null +++ b/plugins/azure-devops/src/components/AzurePullRequestsIcon/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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. + */ + +export { AzurePullRequestsIcon } from './AzurePullRequestsIcon'; From 582a7964b7849681adb5bf8b4e0357d84a075a31 Mon Sep 17 00:00:00 2001 From: hiba-aldalaty Date: Thu, 28 Oct 2021 10:02:09 +0100 Subject: [PATCH 003/557] Add tests for scalable sidebar Signed-off-by: hiba-aldalaty --- .../src/layout/Sidebar/Bar.test.tsx | 104 ++++++++++++++++++ .../src/layout/Sidebar/Bar.tsx | 7 +- .../src/layout/Sidebar/Items.tsx | 1 + .../src/layout/Sidebar/Submenu.tsx | 3 +- .../src/layout/Sidebar/SubmenuItem.tsx | 4 +- 5 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 packages/core-components/src/layout/Sidebar/Bar.test.tsx diff --git a/packages/core-components/src/layout/Sidebar/Bar.test.tsx b/packages/core-components/src/layout/Sidebar/Bar.test.tsx new file mode 100644 index 0000000000..8a28d69e27 --- /dev/null +++ b/packages/core-components/src/layout/Sidebar/Bar.test.tsx @@ -0,0 +1,104 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 { renderInTestApp } from '@backstage/test-utils'; +import { createEvent, fireEvent, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import BuildRoundedIcon from '@material-ui/icons/BuildRounded'; +import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; +import { CatalogSidebarLogo } from './icons/CatalogSidebarLogo'; +import { MiscIcon } from './icons/MiscIcon'; +import { Sidebar, SidebarExpandButton } from './Bar'; +import { SidebarItem, SidebarSearchField } from './Items'; +import { hexToRgb } from '@material-ui/core/styles'; +import { SubmenuItem } from './SubmenuItem'; + +async function renderScalableSidebar() { + await renderInTestApp( + + {}} to="/search" /> + {}} + text="Catalog" + hasSubMenu + submenuTitle="Catalog" + > + + + + + + , + ); +} + +describe('Sidebar', () => { + beforeEach(async () => { + await renderScalableSidebar(); + }); + + describe('Click to Expand', () => { + it('Sidebar should show expanded items when expand button is clicked', async () => { + userEvent.click(screen.getByTestId('sidebar-expand-button')); + expect(await screen.findByText('Create...')).toBeInTheDocument(); + }); + }); + describe('Submenu Items', () => { + it('Extended sidebar with submenu content hidden by default', async () => { + expect(await screen.queryByText('Tools')).not.toBeInTheDocument(); + expect(await screen.queryByText('Misc')).not.toBeInTheDocument(); + }); + + it('Extended sidebar with submenu content visible when hover over submenu items', async () => { + userEvent.hover(screen.getByTestId('item-with-submenu')); + expect(await screen.findByText('Tools')).toBeInTheDocument(); + expect(await screen.findByText('Misc')).toBeInTheDocument(); + }); + + it('Multicategory item in submenu shows drop down on click', async () => { + userEvent.hover(screen.getByTestId('item-with-submenu')); + userEvent.click(screen.getByText('Misc')); + expect(await screen.getByText('dropdown item 1')).toBeInTheDocument(); + expect(await screen.getByText('dropdown item 2')).toBeInTheDocument(); + }); + + it('Dropdown item in submenu renders a link when `to` value is provided', async () => { + userEvent.hover(screen.getByTestId('item-with-submenu')); + userEvent.click(screen.getByText('Misc')); + expect(screen.getByText('dropdown item 1').closest('a')).toHaveAttribute( + 'href', + '/dropdownitemlink', + ); + }); + }); +}); diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index 6fc2ac1cdb..23a4ae1286 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -146,7 +146,12 @@ export const SidebarExpandButton = () => { } return ( -