diff --git a/web/packages/teleport/src/Discover/Navigation/StepItem.tsx b/web/packages/teleport/src/Discover/Navigation/StepItem.tsx
index 95781199e0e29..6bc9b3cb8a03d 100644
--- a/web/packages/teleport/src/Discover/Navigation/StepItem.tsx
+++ b/web/packages/teleport/src/Discover/Navigation/StepItem.tsx
@@ -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';
@@ -52,9 +56,9 @@ export function StepItem(props: StepItemProps) {
return (
- {getBulletIcon({
- Icon: ,
- })}
+ }
+ />
{props.selectedResource.name}
@@ -84,104 +88,28 @@ export function StepItem(props: StepItemProps) {
return (
- {getBulletIcon({
- isDone,
- isActive,
- stepNumber: props.view.index + 1,
- })}
+
{props.view.title}
);
}
-function getBulletIcon({
+function BulletIcon({
isDone,
isActive,
Icon,
stepNumber,
-}: {
- isDone?: boolean;
- isActive?: boolean;
+}: BulletProps & {
Icon?: JSX.Element;
- stepNumber?: number;
}) {
if (Icon) {
return {Icon};
}
- if (isActive) {
- return ;
- }
-
- if (isDone) {
- return ;
- }
-
- return {stepNumber};
+ return ;
}
-
-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;
- }
- }
-`;
diff --git a/web/packages/teleport/src/components/StepNavigation/Bullet.tsx b/web/packages/teleport/src/components/StepNavigation/Bullet.tsx
new file mode 100644
index 0000000000000..80100aba3aac4
--- /dev/null
+++ b/web/packages/teleport/src/components/StepNavigation/Bullet.tsx
@@ -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 ;
+ }
+
+ if (isDone) {
+ return ;
+ }
+
+ return (
+ {stepNumber}
+ );
+}
+
+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};
+ }
+`;
diff --git a/web/packages/teleport/src/components/StepNavigation/Shared.tsx b/web/packages/teleport/src/components/StepNavigation/Shared.tsx
new file mode 100644
index 0000000000000..1e6b2f611b0c4
--- /dev/null
+++ b/web/packages/teleport/src/components/StepNavigation/Shared.tsx
@@ -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;
+ }
+ }
+`;
diff --git a/web/packages/teleport/src/components/StepNavigation/StepNavigation.story.tsx b/web/packages/teleport/src/components/StepNavigation/StepNavigation.story.tsx
new file mode 100644
index 0000000000000..5b68f608980e4
--- /dev/null
+++ b/web/packages/teleport/src/components/StepNavigation/StepNavigation.story.tsx
@@ -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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/web/packages/teleport/src/components/StepNavigation/StepNavigation.test.tsx b/web/packages/teleport/src/components/StepNavigation/StepNavigation.test.tsx
new file mode 100644
index 0000000000000..e2277a825943e
--- /dev/null
+++ b/web/packages/teleport/src/components/StepNavigation/StepNavigation.test.tsx
@@ -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();
+
+ 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();
+
+ 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();
+
+ 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);
+});
diff --git a/web/packages/teleport/src/components/StepNavigation/StepNavigation.tsx b/web/packages/teleport/src/components/StepNavigation/StepNavigation.tsx
new file mode 100644
index 0000000000000..3e3addeb6c4e8
--- /dev/null
+++ b/web/packages/teleport/src/components/StepNavigation/StepNavigation.tsx
@@ -0,0 +1,51 @@
+/**
+ * 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 { Flex } from 'design';
+
+import { StepTitle, StepsContainer } from './Shared';
+import { Bullet } from './Bullet';
+
+export type StepItem = {
+ title: string;
+};
+
+interface NavigationProps {
+ currentStep: number;
+ steps: StepItem[];
+}
+
+export function StepNavigation({ currentStep, steps }: NavigationProps) {
+ const items: JSX.Element[] = [];
+
+ steps.forEach((step, index) => {
+ const isDone = currentStep > index;
+ let isActive = currentStep === index;
+
+ items.push(
+
+
+
+ {step.title}
+
+
+ );
+ });
+
+ return {items};
+}
diff --git a/web/packages/teleport/src/components/StepNavigation/index.ts b/web/packages/teleport/src/components/StepNavigation/index.ts
new file mode 100644
index 0000000000000..6fa8e43bc5e16
--- /dev/null
+++ b/web/packages/teleport/src/components/StepNavigation/index.ts
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+export { StepNavigation } from './StepNavigation';
+export { StepTitle, StepsContainer } from './Shared';
+export { Bullet } from './Bullet';