Skip to content

Commit

Permalink
[feat] add Words component
Browse files Browse the repository at this point in the history
  • Loading branch information
romelperez committed Nov 28, 2017
1 parent acf74c4 commit cf23cb1
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 0 deletions.
2 changes: 2 additions & 0 deletions play/components.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Arwes from '../src/Arwes/Play.md';
import Words from '../src/Words/Play.md';
import Frame from '../src/Frame/Play.md';
import Button from '../src/Button/Play.md';
import Line from '../src/Line/Play.md';
Expand All @@ -10,6 +11,7 @@ import Puffs from '../src/Puffs/Play.md';

const components = [
{ name: 'Arwes', code: Arwes },
{ name: 'Words', code: Words },
{ name: 'Frame', code: Frame },
{ name: 'Button', code: Button },
{ name: 'Line', code: Line },
Expand Down
24 changes: 24 additions & 0 deletions src/Words/Play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
```js
render(
<Arwes>
<h3><Words animate>A cyberpunk UI project</Words></h3>
<p><Words animate>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus, amet
cupiditate laboriosam sunt libero aliquam, consequatur alias ducimus adipisci
nesciunt odit? Odio tenetur et itaque suscipit atque officiis debitis qui.
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus, amet
cupiditate laboriosam sunt libero aliquam, consequatur alias ducimus adipisci
nesciunt odit? Odio tenetur et itaque suscipit atque officiis debitis qui.
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus, amet
cupiditate laboriosam sunt libero aliquam, consequatur alias ducimus adipisci
nesciunt odit? Odio tenetur et itaque suscipit atque officiis debitis qui.
</Words></p>
<p><Words animate layer='success'>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus, amet
cupiditate laboriosam sunt libero aliquam, consequatur alias ducimus adipisci
nesciunt odit? Odio tenetur et itaque suscipit atque officiis debitis qui.
</Words></p>
<p><Words animate layer='alert'>With animations based on SciFi and designs from high technology</Words></p>
</Arwes>
);
```
5 changes: 5 additions & 0 deletions src/Words/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
A plain text wrapper component to apply styles and animation.

```js
<Words animate>Arwes is a cyberpunk UI framework</Words>
```
191 changes: 191 additions & 0 deletions src/Words/Words.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';

export default class Words extends Component {

static propTypes = {
theme: PropTypes.object.isRequired,
classes: PropTypes.object.isRequired,
animate: PropTypes.bool,
show: PropTypes.bool,
layer: PropTypes.oneOf(['primary', 'secondary', 'header', 'control', 'success', 'alert', 'disabled']),
blinkText: PropTypes.string,
animationMaxDuration: PropTypes.number,
children: PropTypes.string.isRequired,
};

static defaultProps = {
animate: false,
show: true,
layer: 'primary',
blinkText: '&#9611;',
animationMaxDuration: 2000,
};

constructor () {
super(...arguments);

this.state = {
text: '',
animating: false,
};
}

componentDidMount () {
if (this.props.animate && this.props.show) {
this.animateIn();
}
}

componentDidUpdate (prevProps) {

const { animate, show, children } = this.props;

const animateChanged = animate !== prevProps.animate;
const showChanged = show !== prevProps.show;
const childrenChanged = children !== prevProps.children;

// Animation changed
if (animate) {
if (showChanged) {
show ? this.animateIn() : this.animateOut();
}
else if (childrenChanged) {
this.animateIn();
}
}

// Not animated anymore
if (!animate && animateChanged) {
this.stopAnimation();
}
}

componentWillUnmount () {
this.stopAnimation();
}

render () {
const {
theme,
classes,
animate,
show,
layer,
blinkText,
animationMaxDuration,
className,
children,
...etc
} = this.props;

const { animating, text } = this.state;

const cls = cx(classes.root, {
[classes.hide]: animate && !show && !animating,
[classes.animating]: animating
}, className);

return (
<span className={cx(cls)} {...etc}>
<span className={classes.children}>
{children}
{animating && (
<span
className={classes.space}
dangerouslySetInnerHTML={{ __html: blinkText }}
/>
)}
</span>
{animating && (
<span className={classes.text}>
{text}
<span
className={classes.blink}
dangerouslySetInnerHTML={{ __html: blinkText }}
/>
</span>
)}
</span>
);
}

animateIn () {
this.cancelNextAnimation();
this.startAnimation(true);
}

animateOut () {
this.cancelNextAnimation();
this.startAnimation(false);
}

stopAnimation () {
this.cancelNextAnimation();
this.setState({ animating: false });
}

cancelNextAnimation () {
window.cancelAnimationFrame(this.currentAnimationFrame);
}

startAnimation (isIn) {
const { theme, children, animationMaxDuration } = this.props;

if (children.length === 0) return;

// 1s / frames per second (FPS)
// 60 FPS are the default in requestAnimationFrame
const interval = 1000 / 60;

// The time it will take to add/remove a character per frame
const realDuration = interval * children.length;

// Duration, min is theme.animTime and max is props.animationMaxDuration
const duration = isIn
? Math.max(Math.min(realDuration, animationMaxDuration), theme.animTime)
: theme.animTime;

this.cancelNextAnimation();

this.setState({
animating: true,
text: isIn ? '' : children
});

const length = children.length;
let start = performance.now();
let progress = null;

const nextAnimation = (timestamp) => {
if (!start) {
start = timestamp;
}

progress = Math.max(timestamp - start, 0);
if (!isIn) {
progress = duration - progress;
}

// partialLength(n) = animationProgressDuration(ms)
// textTotalLength(n) = totalDuration(ms)
const newLength = Math.round((progress * length) / duration);
const text = children.substring(0, newLength);

this.setState({ text });

const continueAnimation = isIn
? newLength <= length
: newLength > 0;

if (continueAnimation) {
this.currentAnimationFrame = window.requestAnimationFrame(nextAnimation);
} else {
this.stopAnimation();
}
};

this.currentAnimationFrame = window.requestAnimationFrame(nextAnimation);
}
}
15 changes: 15 additions & 0 deletions src/Words/Words.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import testSetup from '../test-setup';
import Words from './Words';

const { mount } = testSetup(Words);

describe('Words', function () {

it('Should render without crashing', function () {
mount({
Animation: ({ children }) => children({})
}, 'Random text');
});

});
5 changes: 5 additions & 0 deletions src/Words/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import withStyles from 'react-jss/lib/injectSheet';
import Words from './Words';
import styles from './styles';

export default withStyles(styles)(Words);
47 changes: 47 additions & 0 deletions src/Words/styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export default (theme) => {
return {
root: {
position: 'relative',
display: 'inline-block',
color: props => theme.color[props.layer].base,
},
children: {
display: 'inline-block',
},
space: {
visibility: 'hidden',
opacity: 0,
},
text: {
position: 'absolute',
left: 0,
top: 0,
display: 'inline-block',
opacity: 0,
},
blink: {
display: 'inline-block',
animation: `arwes-words-blink ${theme.animTime}ms step-end infinite`,
},
hide: {
opacity: 0,
},
animating: {
'& $children': {
opacity: 0,
},
'& $text': {
opacity: 1,
},
},

'@keyframes arwes-words-blink': {
'0%, 100%': {
color: 'rgba(0,0,0,0)',
},
'50%': {
color: 'inherit',
},
},
};
};
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Arwes from './Arwes';
import Words from './Words';
import Frame from './Frame';
import Button from './Button';
import Line from './Line';
Expand All @@ -15,6 +16,7 @@ import loader from './tools/loader';

export {
Arwes,
Words,
Frame,
Button,
Line,
Expand Down

0 comments on commit cf23cb1

Please sign in to comment.