Skip to content

Commit 118288b

Browse files
kiraarghyKylie Stewart
authored and
Kylie Stewart
committed
Rewrite slide display and transition logic. (FormidableLabs#691)
* add styled-components dependency * move component files out of respective folders, update imports * Change Deck.js so that it provides a render prop to the child. * Modify Controls.js so that they accept dispatch prop and render buttons. * Modify test.mdx so that it renders Controls and a currentSlide value. * Refactored to use hooks * Added 'SlideElement' logic to Slide. * Finished slide logic * removed Controls as no longer needed. * Add SlideElementWrapper for animated elements * Add note about loop prop for Deck * Add example using JS rather than just MDX (FormidableLabs#694) * moved slideElementWrapper into own component * Implement react spring transitions * added custom transitionEffect prop to Slide component. * Fixed MDX and JS examples * SlideElementWrapper can accept custom springs now! * Added proptypes * Fixed some animation issues with precision. * Added configuration so when going backwards you could skip animations. * Implemented debounced keypress, so can skip animations if skipping through slides. * Fixed some issues with transitionEffects * Add immediate to note s that it is a React-Spring property. * Fix cleanup function for useSlide.
1 parent a99411a commit 118288b

17 files changed

+615
-48
lines changed

.eslintrc

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
{
22
"root": true,
33
"parser": "babel-eslint",
4-
"extends": [
5-
"formidable/configurations/es6-react",
6-
"prettier",
7-
"prettier/react"
8-
],
4+
"extends": ["prettier", "prettier/react", "plugin:react/recommended"],
5+
"plugins": ["react-hooks"],
96
"env": {
107
"browser": true,
118
"commonjs": true,
@@ -25,6 +22,11 @@
2522
"experimentalObjectRestSpread": true
2623
}
2724
},
25+
"settings": {
26+
"react": {
27+
"version": "detect"
28+
}
29+
},
2830
"rules": {
2931
"quotes": [
3032
"error",
@@ -33,6 +35,8 @@
3335
"allowTemplateLiterals": true
3436
}
3537
],
38+
"react-hooks/rules-of-hooks": "error",
39+
"react-hooks/exhaustive-deps": "warn",
3640
"comma-dangle": "off",
3741
"indent": "off",
3842
"space-before-function-paren": "off",
@@ -65,4 +69,4 @@
6569
}
6670
]
6771
}
68-
}
72+
}

CONTRIBUTING.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Contributing
2+
13
Thanks for contributing!
24

35
## Development
@@ -20,7 +22,7 @@ yarn
2022

2123
@TODO
2224

23-
### Before submitting a PR...
25+
### Before submitting a PR
2426

2527
Thanks for taking the time to help us make Spectacle even better! Before you go ahead and submit a PR, make sure that you have done the following:
2628

examples/JS/TestJS.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
import Deck from '../../src/components/Deck.js';
3+
import Slide from '../../src/components/Slide.js';
4+
import SlideElementWrapper from '../../src/components/SlideElementWrapper';
5+
6+
const TestJs = () => (
7+
<Deck loop={true}>
8+
<Slide>
9+
<div>Hi</div>
10+
<b>b</b>
11+
</Slide>
12+
<Slide>
13+
<p> Slide 3! </p>
14+
<SlideElementWrapper elementNum={0}>
15+
<div>Hey, just one "animated" slide element here</div>
16+
</SlideElementWrapper>
17+
</Slide>
18+
<Slide>
19+
<p>I'm a static slide element that should always show</p>
20+
<p>This means that we don't need a SlideElementWrapper</p>
21+
<SlideElementWrapper elementNum={0}>
22+
<p slide-element="true"> ZERO Slide 4 x 3! </p>
23+
</SlideElementWrapper>
24+
<SlideElementWrapper elementNum={1}>
25+
<p slide-element="true"> ONE Slide 4 x 3! </p>
26+
</SlideElementWrapper>
27+
<SlideElementWrapper elementNum={2}>
28+
<p slide-element="true"> TWO Slide 4 x 3! </p>
29+
</SlideElementWrapper>
30+
<p>I'm also a static non-animated "slide element"!</p>
31+
</Slide>
32+
<div>HEY PHIL. YOU DOUBTED US???</div>
33+
</Deck>
34+
);
35+
36+
export default TestJs;

examples/MDX/test.mdx

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<!-- This is an MDX file that is imported in index.js and transpiled to
2+
JSX and then rendered, feel free to change this! -->
3+
4+
import Deck from '../../src/components/Deck.js';
5+
import Slide from '../../src/components/Slide.js';
6+
import SlideElementWrapper from '../../src/components/SlideElementWrapper.js';
7+
import { DeckContext } from '../../src/hooks/useDeck.js';
8+
9+
# Hello, _world_!
10+
11+
<div style={{ padding: '20px', backgroundColor: 'aliceblue' }}>
12+
<h3>This is JSX</h3>
13+
</div>
14+
<Deck loop={true}>
15+
<Slide transitionEffect={{
16+
from: {
17+
width: '100%',
18+
position: 'absolute',
19+
transform: 'translate(-100%, 0%)'
20+
},
21+
enter: {
22+
width: '100%',
23+
position: 'absolute',
24+
transform: 'translate(0, 0%)'
25+
},
26+
leave: {
27+
width: '100%',
28+
position: 'absolute',
29+
transform: 'translate(100%, 0%)'
30+
}
31+
}}>
32+
<div>Hi</div>
33+
<b>b</b>
34+
</Slide>
35+
<Slide>
36+
<p> Slide 3! </p>
37+
<SlideElementWrapper elementNum={1}>
38+
Hey, just one "animated" slide element here
39+
</SlideElementWrapper>
40+
</Slide>
41+
<Slide>
42+
<p>I'm a static slide element that should always show</p>
43+
<p>This means that we don't need a SlideElementWrapper</p>
44+
<SlideElementWrapper elementNum={1} transitionEffect={{from: {transform: 'translate(-100vw)'}, to: {
45+
transform: 'translate(0)'}
46+
}}>
47+
<p slide-element="true"> ZERO Slide 4 x 3! </p>
48+
</SlideElementWrapper>
49+
<SlideElementWrapper elementNum={2}>
50+
<p slide-element="true"> ONE Slide 4 x 3! </p>
51+
</SlideElementWrapper>
52+
<SlideElementWrapper elementNum={3}>
53+
<p slide-element="true"> TWO Slide 4 x 3! </p>
54+
</SlideElementWrapper>
55+
<p>I'm also a static non-animated "slide element"!</p>
56+
</Slide>
57+
</Deck>
58+
59+
- One ah ah ah
60+
- Two ah ah ah
61+
- Two point five
62+
- Three
63+
64+
## Code snippet test
65+
66+
### Using tabs
67+
68+
function useDeck(initialState) {
69+
function reducer(state, action) {
70+
switch (action.type) {
71+
case 'next slide':
72+
return { currentSlide: state.currentSlide + 1 };
73+
case 'prev slide':
74+
return { currentSlide: state.currentSlide - 1 };
75+
default:
76+
return { ...state };
77+
}
78+
}
79+
const [state, dispatch] = React.useReducer(reducer, initialState);
80+
return [state, dispatch];
81+
}
82+
83+
### Using backticks
84+
85+
```js
86+
function useDeck(initialState) {
87+
function reducer(state, action) {
88+
switch (action.type) {
89+
case 'next slide':
90+
return { currentSlide: state.currentSlide + 1 };
91+
case 'prev slide':
92+
return { currentSlide: state.currentSlide - 1 };
93+
default:
94+
return { ...state };
95+
}
96+
}
97+
const [state, dispatch] = React.useReducer(reducer, initialState);
98+
return [state, dispatch];
99+
}
100+
```

index.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import React from 'react';
22
import { render } from 'react-dom';
3-
import MDXDocument from './test.mdx';
3+
4+
// START: test components to try rendering:
5+
import MDXDocument from './examples/MDX/test.mdx';
6+
// import TestJs from './examples/JS/TestJS.js';
7+
// END: test components to try rendering
48

59
/**
610
* Experiment to test MDX -> JSX transpilation through babel.

package.json

+10-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"types": "index.d.ts",
88
"scripts": {
99
"build": "",
10-
"lint": "",
10+
"lint": "eslint --ignore-path .gitignore .",
1111
"lint-fix": "",
1212
"prettier": "prettier \"**/*.{js,json,ts,css,md}\"",
1313
"prettier-fix": "npm run prettier -- --write",
@@ -38,17 +38,19 @@
3838
"@babel/preset-env": "^7.4.5",
3939
"@babel/preset-react": "^7.0.0",
4040
"@mdx-js/loader": "^1.0.0-rc.4",
41+
"babel-eslint": "^10.0.2",
4142
"babel-loader": "^8.0.6",
4243
"builder": "^4.0.0",
43-
"eslint": "^4.19.0",
44+
"eslint": "^5.16.0",
4445
"eslint-config-formidable": "^3.0.0",
4546
"eslint-config-prettier": "^2.9.0",
4647
"eslint-plugin-filenames": "^1.2.0",
4748
"eslint-plugin-import": "^2.9.0",
48-
"eslint-plugin-react": "^7.7.0",
49+
"eslint-plugin-react": "^7.13.0",
50+
"eslint-plugin-react-hooks": "^1.6.0",
4951
"html-webpack-plugin": "^3.2.0",
5052
"prettier": "^1.15.3",
51-
"prop-types": "^15.6.2",
53+
"prop-types": "^15.7.2",
5254
"raw-loader": "^1.0.0",
5355
"react": "^16.7.0",
5456
"react-dom": "^16.7.0",
@@ -57,5 +59,9 @@
5759
"webpack": "^4.33.0",
5860
"webpack-cli": "^3.3.4",
5961
"webpack-dev-server": "^3.7.1"
62+
},
63+
"dependencies": {
64+
"react-spring": "^8.0.25",
65+
"styled-components": "^4.3.1"
6066
}
6167
}

src/components/Deck.js

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import useDeck, { DeckContext } from '../hooks/useDeck';
5+
import isComponentType from '../utils/isComponentType.js';
6+
import { useTransition, animated } from 'react-spring';
7+
8+
/**
9+
* Provides top level state/context provider with useDeck hook
10+
* Should wrap all the presentation components (slides, etc)
11+
*
12+
* Props = {
13+
* loop: bool (pass in true if you want slides to loop)
14+
* transitionEffect: based off of react sprint useTransition
15+
* }
16+
*
17+
* Note: Immediate is a React-Spring property that we pass to the animations
18+
* essentially it skips animations.
19+
*/
20+
21+
const initialState = { currentSlide: 0, immediate: false };
22+
23+
const Deck = ({ children, loop, keyboardControls, ...rest }) => {
24+
// Our default effect for transitioning between slides
25+
const defaultSlideEffect = {
26+
from: {
27+
width: '100%',
28+
position: 'absolute',
29+
transform: 'translate(100%, 0%)'
30+
},
31+
enter: {
32+
width: '100%',
33+
position: 'absolute',
34+
transform: 'translate(0, 0%)'
35+
},
36+
leave: {
37+
width: '100%',
38+
position: 'absolute',
39+
transform: 'translate(-100%, 0%)'
40+
},
41+
config: { precision: 0 }
42+
};
43+
// Check for slides and then number slides.
44+
const filteredChildren = Array.isArray(children)
45+
? children
46+
// filter if is a Slide
47+
.filter(x => isComponentType(x, 'Slide'))
48+
: console.error('No children passed') || [];
49+
50+
// return a wrapped slide with the animated.div + style prop curried
51+
// and a slideNum prop based on iterator
52+
53+
const Slides = filteredChildren.map((
54+
x,
55+
i // eslint-disable-next-line react/display-name
56+
) => ({ style }) => (
57+
<animated.div style={{ ...style }}>
58+
{{
59+
...x,
60+
props: { ...x.props, slideNum: i, keyboardControls }
61+
}}
62+
</animated.div>
63+
));
64+
65+
// Initialise useDeck hook and get state and dispatch off of it
66+
const [state, dispatch] = useDeck(
67+
initialState,
68+
Slides.length,
69+
loop ? true : false,
70+
rest.animationsWhenGoingBack
71+
);
72+
73+
const transitions = useTransition(state.currentSlide, p => p, {
74+
...(filteredChildren[state.currentSlide].props.transitionEffect ||
75+
defaultSlideEffect),
76+
unique: true,
77+
immediate: state.immediate
78+
});
79+
80+
return (
81+
<div
82+
style={{
83+
position: 'relative',
84+
height: '50vh',
85+
width: '100%',
86+
overflowX: 'hidden'
87+
}}
88+
>
89+
<DeckContext.Provider
90+
value={[
91+
state,
92+
dispatch,
93+
Slides.length,
94+
keyboardControls,
95+
rest.animationsWhenGoingBack
96+
]}
97+
>
98+
{transitions.map(({ item, props, key }) => {
99+
const Slide = Slides[item];
100+
return <Slide key={key} style={props} />;
101+
})}
102+
</DeckContext.Provider>
103+
</div>
104+
);
105+
};
106+
107+
Deck.propTypes = {
108+
animationsWhenGoingBack: PropTypes.bool.isRequired,
109+
children: PropTypes.node.isRequired,
110+
keyboardControls: PropTypes.oneOf(['arrows', 'space']),
111+
loop: PropTypes.bool.isRequired
112+
};
113+
114+
Deck.defaultProps = {
115+
loop: false,
116+
keyboardControls: 'arrows',
117+
animationsWhenGoingBack: false
118+
};
119+
120+
export default Deck;

0 commit comments

Comments
 (0)