Skip to content

Commit

Permalink
Web: Create (re-use) step navigator for general use (#32939)
Browse files Browse the repository at this point in the history
* Create (re-use) step navigator for general use

* Re-use shared styles with discover nav
  • Loading branch information
kimlisa authored Oct 4, 2023
1 parent 8a736d7 commit 7a2f8c6
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 89 deletions.
106 changes: 17 additions & 89 deletions web/packages/teleport/src/Discover/Navigation/StepItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@
*/

import React from 'react';
import styled from 'styled-components';
import { Flex } from 'design';
import Flex from 'design/Flex';

import { DiscoverIcon } from 'teleport/Discover/SelectResource/icons';
import { StepTitle, StepsContainer } from 'teleport/components/StepNavigation';
import {
Bullet,
Props as BulletProps,
} from 'teleport/components/StepNavigation/Bullet';

import { StepList } from './StepList';

Expand Down Expand Up @@ -52,9 +56,9 @@ export function StepItem(props: StepItemProps) {
return (
<StepsContainer>
<StepTitle>
{getBulletIcon({
Icon: <DiscoverIcon name={props.selectedResource.icon} />,
})}
<BulletIcon
Icon={<DiscoverIcon name={props.selectedResource.icon} />}
/>
{props.selectedResource.name}
</StepTitle>
</StepsContainer>
Expand Down Expand Up @@ -84,104 +88,28 @@ export function StepItem(props: StepItemProps) {
return (
<StepsContainer active={isDone || isActive}>
<StepTitle>
{getBulletIcon({
isDone,
isActive,
stepNumber: props.view.index + 1,
})}
<BulletIcon
isDone={isDone}
isActive={isActive}
stepNumber={props.view.index + 1}
/>
{props.view.title}
</StepTitle>
</StepsContainer>
);
}

function getBulletIcon({
function BulletIcon({
isDone,
isActive,
Icon,
stepNumber,
}: {
isDone?: boolean;
isActive?: boolean;
}: BulletProps & {
Icon?: JSX.Element;
stepNumber?: number;
}) {
if (Icon) {
return <Flex mr={2}>{Icon}</Flex>;
}

if (isActive) {
return <ActiveBullet />;
}

if (isDone) {
return <CheckedBullet />;
}

return <Bullet>{stepNumber}</Bullet>;
return <Bullet isDone={isDone} isActive={isActive} stepNumber={stepNumber} />;
}

const StepTitle = styled.div`
display: flex;
align-items: center;
`;

const Bullet = styled.span`
height: 14px;
width: 14px;
border: 1px solid #9b9b9b;
font-size: 11px;
border-radius: 50%;
margin-right: 8px;
display: flex;
align-items: center;
justify-content: center;
`;

const ActiveBullet = styled(Bullet)`
border-color: ${props => props.theme.colors.brand};
background: ${props => props.theme.colors.brand};
:before {
content: '';
height: 8px;
width: 8px;
border-radius: 50%;
border: 2px solid ${props => props.theme.colors.levels.surface};
}
`;

const CheckedBullet = styled(Bullet)`
border-color: ${props => props.theme.colors.brand};
background: ${props => props.theme.colors.brand};
:before {
content: '✓';
color: ${props => props.theme.colors.levels.popout};
}
`;

const StepsContainer = styled.div<{ active: boolean }>`
display: flex;
flex-direction: column;
color: ${p => (p.active ? 'inherit' : p.theme.colors.text.slightlyMuted)};
margin-right: 32px;
position: relative;
&:after {
position: absolute;
content: '';
width: 16px;
background: ${({ theme }) => theme.colors.brand};
height: 1px;
top: 50%;
transform: translate(0, -50%);
right: -25px;
}
&:last-of-type {
&:after {
display: none;
}
}
`;
74 changes: 74 additions & 0 deletions web/packages/teleport/src/components/StepNavigation/Bullet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright 2023 Gravitational, Inc.
*
* 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 styled from 'styled-components';

export type Props = {
isDone?: boolean;
isActive?: boolean;
stepNumber?: number;
};

export function Bullet({ isDone, isActive, stepNumber }: Props) {
if (isActive) {
return <ActiveBullet data-testid="bullet-active" />;
}

if (isDone) {
return <CheckedBullet data-testid="bullet-checked" />;
}

return (
<BulletContainer data-testid="bullet-default">{stepNumber}</BulletContainer>
);
}

export const BulletContainer = styled.span`
height: 14px;
width: 14px;
border: 1px solid ${p => p.theme.colors.text.disabled};
font-size: ${p => p.theme.fontSizes[1]}px;
border-radius: 50%;
margin-right: ${p => p.theme.space[2]}px;
display: flex;
align-items: center;
justify-content: center;
`;

export const ActiveBullet = styled(BulletContainer)`
border-color: ${props => props.theme.colors.brand};
background: ${props => props.theme.colors.brand};
:before {
content: '';
height: 8px;
width: 8px;
border-radius: 50%;
border: ${p => p.theme.radii[1]}px solid
${p => p.theme.colors.levels.surface};
}
`;

export const CheckedBullet = styled(BulletContainer)`
border-color: ${props => props.theme.colors.brand};
background: ${props => props.theme.colors.brand};
:before {
content: '✓';
color: ${props => props.theme.colors.levels.popout};
}
`;
47 changes: 47 additions & 0 deletions web/packages/teleport/src/components/StepNavigation/Shared.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright 2023 Gravitational, Inc.
*
* 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 styled from 'styled-components';

export const StepTitle = styled.div`
display: flex;
align-items: center;
`;

export const StepsContainer = styled.div<{ active: boolean }>`
display: flex;
flex-direction: column;
color: ${p => (p.active ? 'inherit' : p.theme.colors.text.slightlyMuted)};
margin-right: ${p => p.theme.space[5]}px;
position: relative;
&:after {
position: absolute;
content: '';
width: 16px;
background: ${({ theme }) => theme.colors.brand};
height: 1px;
top: 50%;
transform: translate(0, -50%);
right: -25px;
}
&:last-of-type {
&:after {
display: none;
}
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright 2023 Gravitational, Inc.
*
* 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 { Box } from 'design';

import { StepNavigation } from './StepNavigation';

export default {
title: 'Teleport/StepNavigation',
};

const steps = [
{ title: 'first title' },
{ title: 'second title' },
{ title: 'third title' },
{ title: 'fourth title' },
{ title: 'fifth title' },
{ title: 'sixth title' },
{ title: 'seventh title' },
{ title: 'eighth title' },
];

export const Examples = () => {
return (
<>
<Box mb={5}>
<StepNavigation currentStep={0} steps={steps.slice(0, 2)} />
</Box>
<Box mb={5}>
<StepNavigation currentStep={1} steps={steps.slice(0, 2)} />
</Box>
<Box mb={5}>
<StepNavigation currentStep={2} steps={steps.slice(0, 2)} />
</Box>
<Box>
<StepNavigation currentStep={3} steps={steps} />
</Box>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Copyright 2023 Gravitational, Inc.
*
* 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 { render, screen } from 'design/utils/testing';

import { StepNavigation } from './StepNavigation';

const steps = [{ title: 'first' }, { title: 'second' }, { title: 'third' }];

test('step 1/3', async () => {
render(<StepNavigation steps={steps} currentStep={0} />);

const firstBullet = screen.getByTestId('bullet-active');
expect(firstBullet).toHaveTextContent('');
expect(firstBullet.parentElement).toHaveTextContent(/first/i);

const uncheckedBullets = screen.getAllByTestId('bullet-default');
expect(uncheckedBullets).toHaveLength(2);

// second bullet
expect(uncheckedBullets[0]).toHaveTextContent(/2/i);
expect(uncheckedBullets[0].parentElement).toHaveTextContent(/second/i);

// last bullet
expect(uncheckedBullets[1]).toHaveTextContent(/3/i);
expect(uncheckedBullets[1].parentElement).toHaveTextContent(/third/i);
});

test('step 2/3', async () => {
render(<StepNavigation steps={steps} currentStep={1} />);

const firstBullet = screen.getByTestId('bullet-checked');
expect(firstBullet).toHaveTextContent('');
expect(firstBullet.parentElement).toHaveTextContent(/first/i);

const secondBullet = screen.getByTestId('bullet-active');
expect(secondBullet).toHaveTextContent('');
expect(secondBullet.parentElement).toHaveTextContent(/second/i);

const lastBullet = screen.getByTestId('bullet-default');
expect(lastBullet).toHaveTextContent(/3/i);
expect(lastBullet.parentElement).toHaveTextContent(/third/i);
});

test('step 3/3', async () => {
render(<StepNavigation steps={steps} currentStep={2} />);

const checkedBullets = screen.getAllByTestId('bullet-checked');
expect(checkedBullets).toHaveLength(2);

// first bullet
expect(checkedBullets[0]).toHaveTextContent('');
expect(checkedBullets[0].parentElement).toHaveTextContent(/first/i);

// second bullet
expect(checkedBullets[1]).toHaveTextContent('');
expect(checkedBullets[1].parentElement).toHaveTextContent(/second/i);

// last bullet
const lastBullet = screen.getByTestId('bullet-active');
expect(lastBullet).toHaveTextContent('');
expect(lastBullet.parentElement).toHaveTextContent(/third/i);
});
Loading

0 comments on commit 7a2f8c6

Please sign in to comment.