From 7a2f8c6b10c4c13df5e2e7fb6eb76158c7eeb764 Mon Sep 17 00:00:00 2001 From: Lisa Kim Date: Wed, 4 Oct 2023 10:35:57 -0700 Subject: [PATCH] Web: Create (re-use) step navigator for general use (#32939) * Create (re-use) step navigator for general use * Re-use shared styles with discover nav --- .../src/Discover/Navigation/StepItem.tsx | 106 +++--------------- .../src/components/StepNavigation/Bullet.tsx | 74 ++++++++++++ .../src/components/StepNavigation/Shared.tsx | 47 ++++++++ .../StepNavigation/StepNavigation.story.tsx | 54 +++++++++ .../StepNavigation/StepNavigation.test.tsx | 76 +++++++++++++ .../StepNavigation/StepNavigation.tsx | 51 +++++++++ .../src/components/StepNavigation/index.ts | 19 ++++ 7 files changed, 338 insertions(+), 89 deletions(-) create mode 100644 web/packages/teleport/src/components/StepNavigation/Bullet.tsx create mode 100644 web/packages/teleport/src/components/StepNavigation/Shared.tsx create mode 100644 web/packages/teleport/src/components/StepNavigation/StepNavigation.story.tsx create mode 100644 web/packages/teleport/src/components/StepNavigation/StepNavigation.test.tsx create mode 100644 web/packages/teleport/src/components/StepNavigation/StepNavigation.tsx create mode 100644 web/packages/teleport/src/components/StepNavigation/index.ts diff --git a/web/packages/teleport/src/Discover/Navigation/StepItem.tsx b/web/packages/teleport/src/Discover/Navigation/StepItem.tsx index 95781199e0e2..6bc9b3cb8a03 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 000000000000..80100aba3aac --- /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 000000000000..1e6b2f611b0c --- /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 000000000000..5b68f608980e --- /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 000000000000..e2277a825943 --- /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 000000000000..3e3addeb6c4e --- /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 000000000000..6fa8e43bc5e1 --- /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';