Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions web/packages/design/src/theme/themes/sharedStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,16 @@ export const sharedStyles: SharedStyles = {
'0px 1px 10px 0px rgba(0, 0, 0, 0.12), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 2px 4px -1px rgba(0, 0, 0, 0.20)',
],
breakpoints: {
// TODO (avatus): remove mobile/tablet/desktop breakpoints in favor of screensize descriptions
mobile: 400 + sidebarWidth,
tablet: 800 + sidebarWidth,
desktop: 1200 + sidebarWidth,
// use these from now on
small: 690,
medium: 1053,
large: 1280,
},
topBarHeight: [48, 56, 72],
space: [0, 4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80],
borders: [
0,
Expand Down
4 changes: 4 additions & 0 deletions web/packages/design/src/theme/themes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,11 @@ export type SharedStyles = {
mobile: number;
tablet: number;
desktop: number;
small: number;
medium: number;
large: number;
};
topBarHeight: number[];
space: number[];
borders: (string | number)[];
typography: typeof typography;
Expand Down
26 changes: 17 additions & 9 deletions web/packages/shared/components/ToolTip/HoverTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,27 @@ import React, { PropsWithChildren, useState } from 'react';
import styled from 'styled-components';
import { Popover, Flex, Text } from 'design';

type OriginProps = {
vertical: string;
horizontal: string;
};

export const HoverTooltip: React.FC<
PropsWithChildren<{
tipContent: string | undefined;
showOnlyOnOverflow?: boolean;
className?: string;
anchorOrigin?: OriginProps;
transformOrigin?: OriginProps;
}>
> = ({ tipContent, children, showOnlyOnOverflow = false, className }) => {
> = ({
tipContent,
children,
showOnlyOnOverflow = false,
className,
anchorOrigin = { vertical: 'top', horizontal: 'center' },
transformOrigin = { vertical: 'bottom', horizontal: 'center' },
}) => {
const [anchorEl, setAnchorEl] = useState<Element | undefined>();
const open = Boolean(anchorEl);

Expand Down Expand Up @@ -70,14 +84,8 @@ export const HoverTooltip: React.FC<
onClose={handlePopoverClose}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
anchorOrigin={anchorOrigin}
transformOrigin={transformOrigin}
disableRestoreFocus
>
<StyledOnHover
Expand Down
4 changes: 2 additions & 2 deletions web/packages/teleport/src/Main/Main.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ test('renders without questionnaire prop', () => {
</MemoryRouter>
);

expect(screen.getByTestId('title')).toBeInTheDocument();
expect(screen.getByTestId('teleport-logo')).toBeInTheDocument();
});

test('displays invite collaborators feedback if present', () => {
Expand Down Expand Up @@ -142,5 +142,5 @@ test('renders without invite collaborators feedback enabled', () => {
</MemoryRouter>
);

expect(screen.getByTestId('title')).toBeInTheDocument();
expect(screen.getByTestId('teleport-logo')).toBeInTheDocument();
});
23 changes: 11 additions & 12 deletions web/packages/teleport/src/Main/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,15 @@ import useTeleport from 'teleport/useTeleport';
import { TopBar } from 'teleport/TopBar';
import { BannerList } from 'teleport/components/BannerList';
import { storageService } from 'teleport/services/storageService';

import { ClusterAlert, LINK_LABEL } from 'teleport/services/alerts/alerts';

import { Navigation } from 'teleport/Navigation';

import { useAlerts } from 'teleport/components/BannerList/useAlerts';

import { FeaturesContextProvider, useFeatures } from 'teleport/FeaturesContext';

import {
getFirstRouteForCategory,
NavigationProps,
Navigation,
} from 'teleport/Navigation/Navigation';

import { NavigationCategory } from 'teleport/Navigation/categories';

import { TopBarProps } from 'teleport/TopBar/TopBar';
import { QuestionnaireProps } from 'teleport/Welcome/NewCredentials';

import { MainContainer } from './MainContainer';
Expand All @@ -72,7 +65,7 @@ export interface MainProps {
features: TeleportFeature[];
billingBanners?: ReactNode[];
Questionnaire?: (props: QuestionnaireProps) => React.ReactElement;
navigationProps?: NavigationProps;
topBarProps?: TopBarProps;
inviteCollaboratorsFeedback?: ReactNode;
}

Expand Down Expand Up @@ -170,18 +163,24 @@ export function Main(props: MainProps) {

return (
<FeaturesContextProvider value={features}>
<TopBar
CustomLogo={
props.topBarProps?.showPoweredByLogo
? props.topBarProps.CustomLogo
: null
}
/>
<BannerList
banners={banners}
customBanners={props.customBanners}
billingBanners={featureFlags.billing && props.billingBanners}
onBannerDismiss={dismissAlert}
>
<MainContainer>
<Navigation {...props.navigationProps} />
<Navigation />
<HorizontalSplit>
<ContentMinWidth>
<Suspense fallback={null}>
<TopBar />
<FeatureRoutes lockedFeatures={ctx.lockedFeatures} />
</Suspense>
</ContentMinWidth>
Expand Down
7 changes: 7 additions & 0 deletions web/packages/teleport/src/Main/MainContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,11 @@ export const MainContainer = styled.div`
flex: 1;
min-height: 0;
--sidebar-width: 256px;
margin-top: ${p => p.theme.topBarHeight[0]}px;
@media screen and (min-width: ${p => p.theme.breakpoints.small}px) {
margin-top: ${p => p.theme.topBarHeight[1]}px;
}
@media screen and (min-width: ${p => p.theme.breakpoints.large}px) {
margin-top: ${p => p.theme.topBarHeight[2]}px;
}
`;
144 changes: 10 additions & 134 deletions web/packages/teleport/src/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React, { useCallback, useEffect, useState } from 'react';
import styled, { useTheme } from 'styled-components';
import { matchPath, useHistory, useLocation } from 'react-router';
import { Image } from 'design';
import React from 'react';
import styled from 'styled-components';
import { matchPath, useLocation, useHistory } from 'react-router';

import { NavigationSwitcher } from 'teleport/Navigation/NavigationSwitcher';
import cfg from 'teleport/config';
import {
NAVIGATION_CATEGORIES,
NavigationCategory,
} from 'teleport/Navigation/categories';
import { useFeatures } from 'teleport/FeaturesContext';
import { NavigationCategoryContainer } from 'teleport/Navigation/NavigationCategoryContainer';
import { NotificationKind } from 'teleport/stores/storeNotifications';

import { useTeleport } from '..';

import logoLight from './logoLight.svg';
import logoDark from './logoDark.svg';
import logoPoweredBy from './logoPoweredBy.svg';

import type * as history from 'history';

Expand Down Expand Up @@ -104,74 +95,14 @@ function getCategoryForRoute(
return feature.category;
}

export function Navigation({
CustomLogo,
showPoweredByLogo = false,
}: NavigationProps) {
export function Navigation() {
const features = useFeatures();
const history = useHistory();
const location = useLocation();
const ctx = useTeleport();

const [view, setView] = useState(
const view =
getCategoryForRoute(features, history.location) ||
NavigationCategory.Resources
);

const [previousRoute, setPreviousRoute] = useState<{
[category: string]: string;
}>({});

const handleLocationChange = useCallback(
(next: history.Location<unknown> | Location) => {
const previousPathName = location.pathname;

const category = getCategoryForRoute(features, next);
const previousCategory = getCategoryForRoute(features, location);

if (category && category !== view) {
setView(category);

if (previousCategory) {
setPreviousRoute(previous => ({
...previous,
[previousCategory]: previousPathName,
}));
}
}
},
[location, view]
);

useEffect(() => {
return history.listen(handleLocationChange);
}, [history, location.pathname, features, view]);

const handlePopState = useCallback(
(event: PopStateEvent) => {
handleLocationChange((event.currentTarget as Window).location);
},
[view]
);

useEffect(() => {
window.addEventListener('popstate', handlePopState);

return () => window.removeEventListener('popstate', handlePopState);
}, [handlePopState]);

const handleCategoryChange = useCallback(
(category: NavigationCategory) => {
if (view === category) {
return;
}

history.push(
previousRoute[category] || getFirstRouteForCategory(features, category)
);
},
[view, previousRoute]
);
NavigationCategory.Resources;

const categories = NAVIGATION_CATEGORIES.map((category, index) => (
<NavigationCategoryContainer
Expand All @@ -183,71 +114,16 @@ export function Navigation({

const feature = getFeatureForRoute(features, location);

if (feature?.hideNavigation) {
if (
feature?.hideNavigation ||
feature?.category !== NavigationCategory.Management
) {
return null;
}

return (
<NavigationContainer>
{CustomLogo ? <CustomLogo /> : <NavigationLogo />}

{ctx.getFeatureFlags().managementSection && (
<NavigationSwitcher
onChange={handleCategoryChange}
value={view}
items={[
{ category: NavigationCategory.Resources },
{
category: NavigationCategory.Management,
requiresAttention: ctx.storeNotifications.hasNotificationsByKind(
NotificationKind.AccessList
),
},
]}
/>
)}

<CategoriesContainer>{categories}</CategoriesContainer>
{showPoweredByLogo && <PoweredByLogo />}
</NavigationContainer>
);
}

const NavigationLogo = () => {
const theme = useTheme();

return (
<Image
src={theme.type === 'dark' ? logoDark : logoLight}
height="32px"
width="fit-content"
style={{
marginTop: '20px',
marginLeft: '32px',
marginBottom: '20px',
}}
alt="teleport logo"
/>
);
};

const PoweredByLogo = () => {
return (
<Image
src={logoPoweredBy}
height="48px"
width="fit-content"
style={{
marginTop: '28px',
marginLeft: '32px',
marginBottom: '36px',
}}
alt="powered by teleport"
/>
);
};

export type NavigationProps = {
CustomLogo?: () => React.ReactElement;
showPoweredByLogo?: boolean;
};
5 changes: 3 additions & 2 deletions web/packages/teleport/src/Navigation/NavigationItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

import React from 'react';
import styled from 'styled-components';

import { render, screen } from 'design/utils/testing';

Expand Down Expand Up @@ -52,7 +53,7 @@ class MockUserFeature implements TeleportFeature {

navigationItem = {
title: NavTitle.Users,
icon: <div />,
icon: styled.div,
exact: true,
getLink(clusterId: string) {
return generatePath('/web/cluster/:clusterId/feature', { clusterId });
Expand All @@ -76,7 +77,7 @@ class MockAccessListFeature implements TeleportFeature {

navigationItem = {
title: NavTitle.AccessLists,
icon: <div />,
icon: styled.div,
exact: true,
getLink(clusterId: string) {
return generatePath('/web/cluster/:clusterId/feature', { clusterId });
Expand Down
Loading