meriadec
6 years ago
4 changed files with 195 additions and 0 deletions
@ -0,0 +1,65 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import styled, { css, keyframes } from 'styled-components' |
|||
|
|||
import { colors } from 'styles/theme' |
|||
|
|||
const animIndeterminate = keyframes` |
|||
0% { |
|||
transform: scaleX(0) translate3d(0, 0, 0); |
|||
} |
|||
50% { |
|||
transform: scaleX(1) translate3d(100%, 0, 0); |
|||
} |
|||
100% { |
|||
transform: scaleX(0) translate3d(0, 0, 0); |
|||
} |
|||
` |
|||
|
|||
const Outer = styled.div` |
|||
background-color: ${colors.fog}; |
|||
border-radius: 3px; |
|||
overflow: hidden; |
|||
height: 10px; |
|||
width: ${p => p.width}px; |
|||
position: relative; |
|||
` |
|||
|
|||
const Inner = styled.div` |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: ${colors.wallet}; |
|||
transform-origin: center left; |
|||
|
|||
${p => |
|||
p.progress === 0 |
|||
? css` |
|||
animation: ${animIndeterminate} 2s cubic-bezier(0.61, 0.01, 0.39, 1.03) infinite; |
|||
` |
|||
: css` |
|||
transform: scaleX(${p => p.progress}); |
|||
transition: 150ms ease-out transform; |
|||
`};
|
|||
` |
|||
|
|||
type Props = { |
|||
progress: number, |
|||
width: number, |
|||
} |
|||
|
|||
class ProgressBar extends PureComponent<Props> { |
|||
render() { |
|||
const { progress, width } = this.props |
|||
return ( |
|||
<Outer width={width}> |
|||
<Inner progress={progress} /> |
|||
</Outer> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default ProgressBar |
@ -0,0 +1,16 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
import { storiesOf } from '@storybook/react' |
|||
import { number } from '@storybook/addon-knobs' |
|||
|
|||
import ProgressBar from 'components/ProgressBar' |
|||
|
|||
const stories = storiesOf('Components', module) |
|||
|
|||
stories.add('ProgressBar', () => ( |
|||
<ProgressBar |
|||
progress={number('progress', 0, { min: 0, max: 1, step: 0.05 })} |
|||
width={number('width', 200, { min: 50, max: 500, step: 10 })} |
|||
/> |
|||
)) |
@ -0,0 +1,98 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import styled, { css, keyframes } from 'styled-components' |
|||
|
|||
import { colors } from 'styles/theme' |
|||
|
|||
import Text from 'components/base/Text' |
|||
|
|||
const STROKE_WIDTH = 10 |
|||
|
|||
type Props = { |
|||
progress: number, |
|||
size: number, |
|||
} |
|||
|
|||
const animIndeterminate = keyframes` |
|||
0% { |
|||
} |
|||
50% { |
|||
} |
|||
100% { |
|||
} |
|||
` |
|||
|
|||
const InnerCircle = styled.circle` |
|||
transform-origin: 50% 50%; |
|||
${p => |
|||
p.progress === 0 |
|||
? css` |
|||
animation: ${animIndeterminate} 3s cubic-bezier(0.61, 0.01, 0.39, 1.03) infinite; |
|||
` |
|||
: css` |
|||
transition: stroke-dashoffset 0.35s; |
|||
transform: rotate(-90deg); |
|||
`};
|
|||
` |
|||
|
|||
const Container = styled.div` |
|||
position: relative; |
|||
width: ${p => p.size}px; |
|||
height: ${p => p.size}px; |
|||
` |
|||
|
|||
const TextContainer = styled.div` |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
` |
|||
|
|||
class ProgressCircle extends PureComponent<Props> { |
|||
render() { |
|||
const { size, progress } = this.props |
|||
const radius = size / 2 |
|||
const normalizedRadius = radius - STROKE_WIDTH / 2 |
|||
const circumference = normalizedRadius * 2 * Math.PI |
|||
const strokeDashoffset = circumference - progress * circumference |
|||
|
|||
return ( |
|||
<Container size={size}> |
|||
<TextContainer> |
|||
<Text ff="Museo Sans|Bold" color="graphite" fontSize={6}> |
|||
{`${Math.round(progress * 100)}%`} |
|||
</Text> |
|||
</TextContainer> |
|||
<svg height={size} width={size}> |
|||
<circle |
|||
stroke={colors.fog} |
|||
fill="transparent" |
|||
strokeWidth={STROKE_WIDTH} |
|||
style={{ strokeDashoffset }} |
|||
r={normalizedRadius} |
|||
cx={radius} |
|||
cy={radius} |
|||
/> |
|||
<InnerCircle |
|||
progress={progress} |
|||
stroke={colors.wallet} |
|||
fill="transparent" |
|||
strokeWidth={STROKE_WIDTH} |
|||
strokeDasharray={`${circumference} ${circumference}`} |
|||
style={{ strokeDashoffset }} |
|||
r={normalizedRadius} |
|||
cx={radius} |
|||
cy={radius} |
|||
/> |
|||
</svg> |
|||
</Container> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default ProgressCircle |
@ -0,0 +1,16 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
import { storiesOf } from '@storybook/react' |
|||
import { number } from '@storybook/addon-knobs' |
|||
|
|||
import ProgressCircle from 'components/ProgressCircle' |
|||
|
|||
const stories = storiesOf('Components', module) |
|||
|
|||
stories.add('ProgressCircle', () => ( |
|||
<ProgressCircle |
|||
progress={number('progress', 0, { min: 0, max: 1, step: 0.01 })} |
|||
size={number('width', 150, { min: 50, max: 500, step: 10 })} |
|||
/> |
|||
)) |
Loading…
Reference in new issue