-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #986 from glints-dev/feature/steps-component
Steps: add new component
- Loading branch information
Showing
12 changed files
with
408 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import React from 'react'; | ||
import { Typography } from '../Typography'; | ||
import { | ||
CircleDiv, | ||
StepItemContainer, | ||
StepItemWrapper, | ||
VerticalLine, | ||
VerticalLineWrapper, | ||
} from './StepStyle'; | ||
import { Icon } from '../Icon'; | ||
import { Neutral } from '../utilities/colors'; | ||
|
||
export interface StepProps { | ||
/** Styles of step, predetermined from parent component, or you can overwrite this */ | ||
variant?: 'pending' | 'completed' | 'processing' | 'error'; | ||
/** Label given to the step component */ | ||
label?: string; | ||
/** Step number to be shown, by default it's 1,2,3,... from the parent component, or you can overwrite this */ | ||
index?: number; | ||
type?: 'normal' | 'dot'; | ||
} | ||
|
||
export const Step = React.forwardRef<HTMLDivElement, StepProps>(function Step( | ||
{ variant = 'pending', label = '', index = 0, type = 'normal' }: StepProps, | ||
ref | ||
) { | ||
return ( | ||
<StepItemContainer className="step-item-container"> | ||
<StepItemWrapper | ||
ref={ref} | ||
data-dot={type === 'dot'} | ||
className="step-item-wrapper" | ||
> | ||
<CircleDiv data-variant={variant} data-dot={type === 'dot'}> | ||
{variant === 'completed' && ( | ||
<Icon name="ri-check" className="circle-icon" /> | ||
)} | ||
{variant === 'error' && ( | ||
<Icon name="ri-close" className="circle-icon" /> | ||
)} | ||
{variant === 'processing' && ( | ||
<Typography | ||
as="span" | ||
variant="caption" | ||
color={Neutral.B100} | ||
className="circle-icon" | ||
> | ||
{index} | ||
</Typography> | ||
)} | ||
{variant === 'pending' && ( | ||
<Typography | ||
as="span" | ||
variant="caption" | ||
color={Neutral.B40} | ||
className="circle-icon" | ||
> | ||
{index} | ||
</Typography> | ||
)} | ||
</CircleDiv> | ||
<Typography | ||
as="div" | ||
variant={ | ||
variant === 'processing' || variant === 'error' ? 'body2' : 'body1' | ||
} | ||
color={ | ||
variant === 'pending' || variant === 'completed' | ||
? Neutral.B40 | ||
: Neutral.B18 | ||
} | ||
> | ||
{label} | ||
</Typography> | ||
</StepItemWrapper> | ||
<VerticalLineWrapper data-dot={type === 'dot'}> | ||
<VerticalLine data-variant={variant} data-dot={type === 'dot'} /> | ||
</VerticalLineWrapper> | ||
</StepItemContainer> | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import styled from 'styled-components'; | ||
import { Breakpoints } from '..'; | ||
import { space4, space12, space16 } from '../utilities/spacing'; | ||
import { Blue, Neutral, Red } from '../utilities/colors'; | ||
import { borderRadiusHalf } from '../utilities/borderRadius'; | ||
|
||
export const StepItemContainer = styled.div` | ||
&:last-child > div:last-child { | ||
display: none; | ||
} | ||
`; | ||
|
||
export const StepItemWrapper = styled.div` | ||
display: flex; | ||
align-items: center; | ||
gap: ${space12}; | ||
cursor: default; | ||
&[data-dot='true'] { | ||
gap: ${space16}; | ||
} | ||
`; | ||
|
||
export const CircleDiv = styled.div` | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
height: 28px; | ||
width: 28px; | ||
border-radius: ${borderRadiusHalf}; | ||
svg { | ||
height: 16px; | ||
width: 16px; | ||
} | ||
&[data-variant='pending'] { | ||
background-color: ${Neutral.B95}; | ||
} | ||
&[data-variant='completed'] { | ||
background-color: ${Blue.S08}; | ||
svg { | ||
fill: ${Blue.S99}; | ||
} | ||
} | ||
&[data-variant='processing'] { | ||
background-color: ${Blue.S99}; | ||
} | ||
&[data-variant='error'] { | ||
background-color: ${Red.B93}; | ||
svg { | ||
fill: ${Neutral.B100}; | ||
} | ||
} | ||
&[data-dot='true'] { | ||
height: 10px; | ||
width: 10px; | ||
> .circle-icon { | ||
display: none; | ||
} | ||
&[data-variant='completed'] { | ||
background-color: ${Blue.S99}; | ||
} | ||
} | ||
@media (max-width: ${Breakpoints.large}) { | ||
height: 24px; | ||
width: 24px; | ||
svg { | ||
height: 14px; | ||
width: 14px; | ||
} | ||
} | ||
`; | ||
|
||
export const VerticalLineWrapper = styled.div` | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
padding: ${space4} 0; | ||
height: 64px; | ||
width: 28px; | ||
&[data-dot='true'] { | ||
width: 10px; | ||
} | ||
@media (max-width: ${Breakpoints.large}) { | ||
width: 24px; | ||
} | ||
`; | ||
|
||
export const VerticalLine = styled.div` | ||
width: 2px; | ||
height: 100%; | ||
background-color: ${Neutral.B85}; | ||
&[data-variant='completed'] { | ||
background-color: ${Blue.S99}; | ||
} | ||
&[data-dot='true'] { | ||
width: 1.5px; | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import React from 'react'; | ||
import { Meta, Story } from '@storybook/react'; | ||
import { ButtonGroup } from '../ButtonGroup'; | ||
import { Button } from '../Button'; | ||
|
||
import { BaseContainer } from '../../Layout/GlintsContainer/GlintsContainer'; | ||
import { withGlintsPortalContainer } from '../../helpers/storybook/Decorators'; | ||
import { Steps, StepsProps } from './Steps'; | ||
|
||
export default { | ||
title: '@next/Steps', | ||
component: Steps, | ||
decorators: [ | ||
Story => <BaseContainer>{Story()}</BaseContainer>, | ||
withGlintsPortalContainer, | ||
], | ||
} as Meta; | ||
|
||
const Template: Story<StepsProps> = args => { | ||
const [currentStep, setCurrentStep] = React.useState<number>(1); | ||
const [errorSteps, setErrorSteps] = React.useState<number[]>([]); | ||
|
||
const handlePrevClick = () => { | ||
if (currentStep > 1) setCurrentStep(currentStep - 1); | ||
}; | ||
|
||
const handleNextClick = () => { | ||
if (currentStep < 6) setCurrentStep(currentStep + 1); | ||
}; | ||
|
||
const handleSetError = (index: number) => { | ||
setErrorSteps(prevErrorSteps => { | ||
if (prevErrorSteps.includes(index)) { | ||
return prevErrorSteps.filter(item => item !== index); | ||
} else { | ||
return [...prevErrorSteps, index]; | ||
} | ||
}); | ||
}; | ||
|
||
return ( | ||
<> | ||
<Steps {...args} currentStep={currentStep} errorSteps={errorSteps}> | ||
<Steps.Step label="Label 1" /> | ||
<Steps.Step label="Label 2" /> | ||
<Steps.Step label="Label 3" /> | ||
<Steps.Step label="Label 4" /> | ||
<Steps.Step label="Label 5" /> | ||
</Steps> | ||
<div style={{ margin: '16px 0 8px' }}> | ||
Current Step: <b>{currentStep}</b> | ||
</div> | ||
<div style={{ margin: '8px 0' }}> | ||
Error Steps: <b>[{errorSteps.join(', ')}]</b> | ||
</div> | ||
<ButtonGroup> | ||
<Button onClick={handlePrevClick} data-testid="prev-button"> | ||
Prev | ||
</Button> | ||
<Button onClick={handleNextClick} data-testid="next-button"> | ||
Next | ||
</Button> | ||
<Button | ||
onClick={() => handleSetError(currentStep)} | ||
data-testid="error-button" | ||
> | ||
Toggle Error | ||
</Button> | ||
</ButtonGroup> | ||
</> | ||
); | ||
}; | ||
|
||
export const Interactive = Template.bind({}); | ||
Interactive.args = { | ||
type: 'normal', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import React from 'react'; | ||
import { Step, StepProps } from './Step'; | ||
|
||
export interface StepsProps extends React.HTMLAttributes<HTMLDivElement> { | ||
/** Step index (1-indexed) marked as processing */ | ||
currentStep: number; | ||
/** List of indexes (1-indexed) to be marked as error */ | ||
errorSteps?: number[]; | ||
/** Step components as child are required (1-indexed) */ | ||
children?: React.ReactElement<StepProps>[]; | ||
/** If dot type, display dot only; default is normal; automatically passed to all children */ | ||
type?: 'normal' | 'dot'; | ||
} | ||
|
||
export const StepsComponent = React.forwardRef<HTMLDivElement, StepsProps>( | ||
function Collapse( | ||
{ | ||
currentStep = 0, | ||
errorSteps = [], | ||
children, | ||
type = 'normal', | ||
...props | ||
}: StepsProps, | ||
ref | ||
) { | ||
return ( | ||
<div ref={ref} {...props}> | ||
{React.Children.map(children, (child, index) => { | ||
const variant: StepProps['variant'] = errorSteps.includes(index + 1) | ||
? 'error' | ||
: index + 1 === currentStep | ||
? 'processing' | ||
: index + 1 < currentStep | ||
? 'completed' | ||
: 'pending'; | ||
const childVariant = | ||
(child.props as Pick<StepProps, 'variant'>)?.variant || variant; | ||
const childIndex = | ||
(child.props as Pick<StepProps, 'index'>)?.index || index + 1; | ||
|
||
return ( | ||
<child.type | ||
{...child.props} | ||
variant={childVariant} | ||
index={childIndex} | ||
type={type} | ||
/> | ||
); | ||
})} | ||
</div> | ||
); | ||
} | ||
); | ||
|
||
export const Steps = Object.assign(StepsComponent, { | ||
Step: Step, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { test, expect } from '@playwright/test'; | ||
import { StepsPage } from './stepsPage'; | ||
|
||
test('Steps - default', async ({ page }) => { | ||
const stepsPage = new StepsPage(page); | ||
await stepsPage.goto(); | ||
|
||
await expect(stepsPage.container).toHaveScreenshot('steps-default.png'); | ||
}); | ||
|
||
test('Steps - progress', async ({ page }) => { | ||
const stepsPage = new StepsPage(page); | ||
await stepsPage.goto(); | ||
|
||
await stepsPage.nextButton.click(); | ||
await stepsPage.nextButton.click(); | ||
|
||
await expect(stepsPage.container).toHaveScreenshot('steps-progress.png'); | ||
}); | ||
|
||
test('Steps - error', async ({ page }) => { | ||
const stepsPage = new StepsPage(page); | ||
await stepsPage.goto(); | ||
|
||
await stepsPage.nextButton.click(); | ||
await stepsPage.nextButton.click(); | ||
await stepsPage.errorButton.click(); | ||
|
||
await expect(stepsPage.container).toHaveScreenshot('steps-error.png'); | ||
}); | ||
|
||
test('Steps - completed', async ({ page }) => { | ||
const stepsPage = new StepsPage(page); | ||
await stepsPage.goto(); | ||
|
||
await stepsPage.nextButton.click(); | ||
await stepsPage.nextButton.click(); | ||
await stepsPage.nextButton.click(); | ||
await stepsPage.nextButton.click(); | ||
await stepsPage.nextButton.click(); | ||
|
||
await expect(stepsPage.container).toHaveScreenshot('steps-completed.png'); | ||
}); | ||
|
||
test('Steps - small screen', async ({ page }) => { | ||
page.setViewportSize({ width: 768, height: 600 }); | ||
const stepsPage = new StepsPage(page); | ||
await stepsPage.goto(); | ||
|
||
await stepsPage.nextButton.click(); | ||
await stepsPage.nextButton.click(); | ||
await stepsPage.errorButton.click(); | ||
await stepsPage.nextButton.click(); | ||
|
||
await expect(stepsPage.container).toHaveScreenshot('steps-small-screen.png'); | ||
}); | ||
|
||
test('Steps - dot style', async ({ page }) => { | ||
const stepsPage = new StepsPage(page); | ||
await stepsPage.goto('args=type:dot'); | ||
|
||
await stepsPage.nextButton.click(); | ||
await stepsPage.nextButton.click(); | ||
await stepsPage.errorButton.click(); | ||
await stepsPage.nextButton.click(); | ||
|
||
await expect(stepsPage.container).toHaveScreenshot('steps-dot.png'); | ||
}); |
Binary file added
BIN
+19.1 KB
test/e2e/steps/steps.spec.ts-snapshots/steps-completed-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+19.6 KB
test/e2e/steps/steps.spec.ts-snapshots/steps-default-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+20.3 KB
test/e2e/steps/steps.spec.ts-snapshots/steps-error-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+20 KB
test/e2e/steps/steps.spec.ts-snapshots/steps-progress-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+17.2 KB
test/e2e/steps/steps.spec.ts-snapshots/steps-small-screen-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.