Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Travis Status][trav_img]][trav_site]
ReactJS based Presentation Library

[Spectacle Boilerplate MDX](https://github.com/FormidableLabs/spectacle-boilerplate-mdx/)
[Spectacle Boilerplate MDX](https://github.com/FormidableLabs/spectacle-boilerplate-mdx/)
[Spectacle Boilerplate](https://github.com/FormidableLabs/spectacle-boilerplate/)

Have a question about Spectacle? Submit an issue in this repository using the "Question" template.
Expand Down Expand Up @@ -465,20 +465,21 @@ In Spectacle, presentations are composed of a set of base tags. We can separate

The Deck tag is the root level tag for your presentation. It supports the following props:

| Name | PropType | Description | Default |
| ----------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| autoplay | PropTypes.bool | Automatically advance slides. | `false` |
| autoplayDuration | PropTypes.number | Accepts integer value in milliseconds for global autoplay duration. | `7000` |
| autoplayLoop | PropTypes.bool | Keep slides in loop. | `true` |
| controls | PropTypes.bool | Show control arrows when not in fullscreen. | `true` |
| contentHeight | PropTypes.numbers | Baseline content area height. | `700px` |
| contentWidth | PropTypes.numbers | Baseline content area width. | `1000px` |
| disableKeyboardControls | PropTypes.bool | Toggle keyboard control. | `false` |
| history | PropTypes.object | Accepts custom configuration for [history](https://github.com/ReactTraining/history). | |
| progress | PropTypes.string | Accepts `pacman`, `bar`, `number` or `none`. To override the color, change the 'quaternary' color in the theme. | `pacman` |
| theme | PropTypes.object | Accepts a theme object for styling your presentation. | |
| transition | PropTypes.array | Accepts `slide`, `zoom`, `fade` or `spin`, and can be combined. Sets global slide transitions. **Note: If you use the 'scale' transition, fitted text won't work in Safari.** | |
| transitionDuration | PropTypes.number | Accepts integer value in milliseconds for global transition duration. | `500` |
| Name | PropType | Description | Default |
| ----------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| autoplay | PropTypes.bool | Automatically advance slides. | `false` |
| autoplayDuration | PropTypes.number | Accepts integer value in milliseconds for global autoplay duration. | `7000` |
| autoplayLoop | PropTypes.bool | Keep slides in loop. | `true` |
| controls | PropTypes.bool | Show control arrows when not in fullscreen. | `true` |
| contentHeight | PropTypes.numbers | Baseline content area height. | `700px` |
| contentWidth | PropTypes.numbers | Baseline content area width. | `1000px` |
| disableKeyboardControls | PropTypes.bool | Toggle keyboard control. | `false` |
| onStateChange | PropTypes.func | Called whenever a new slide becomes visible with the arguments `(previousState, nextState)` where state refers to the outgoing and incoming `<Slide />`'s `state` props, respectively. The default implementation attaches the current state as a class to the document root. | see description |
| history | PropTypes.object | Accepts custom configuration for [history](https://github.com/ReactTraining/history). | |
| progress | PropTypes.string | Accepts `pacman`, `bar`, `number` or `none`. To override the color, change the 'quaternary' color in the theme. | `pacman` |
| theme | PropTypes.object | Accepts a theme object for styling your presentation. | |
| transition | PropTypes.array | Accepts `slide`, `zoom`, `fade` or `spin`, and can be combined. Sets global slide transitions. **Note: If you use the 'scale' transition, fitted text won't work in Safari.** | |
| transitionDuration | PropTypes.number | Accepts integer value in milliseconds for global transition duration. | `500` |

<a name="slide-base"></a>

Expand All @@ -497,6 +498,7 @@ The slide tag represents each slide in the presentation. Giving a slide tag an `
| notes | PropTypes.string | Text which will appear in the presenter mode. Can be HTML. | |
| onActive | PropTypes.func | Optional function that is called with the slide index when the slide comes into view. | |
| progressColor | PropTypes.string | Used to override color of progress elements on a per slide basis, accepts color aliases, or valid color values. | `quaternary` color set by theme |
| state | PropTypes.string | Used to indicate that the deck is in a specific state. Inspired by [Reveal.js](https://github.com/hakimel/reveal.js)'s `data-state` attribute | |
| transition | PropTypes.array | Used to override transition prop on a per slide basis, accepts `slide`, `zoom`, `fade`, `spin`, or a [function](#transition-function), and can be combined. This will affect both enter and exit transitions. **Note: If you use the 'scale' transition, fitted text won't work in Safari.** | Set by `Deck`'s `transition` prop |
| transitionIn | PropTypes.array | Specifies the slide transition when the slide comes into view. Accepts the same values as transition. |
| transitionOut | PropTypes.array | Specifies the slide transition when the slide exits. Accepts the same values as transition. | Set by `Deck`'s `transition` prop |
Expand Down
35 changes: 35 additions & 0 deletions src/components/deck.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ import Manager from './manager';

const store = configureStore();

export function defaultOnStateChange(prevState, nextState) {
if (nextState) {
document.documentElement.classList.add(nextState);
}

if (prevState) {
document.documentElement.classList.remove(prevState);
}
}

export default class Deck extends Component {
static displayName = 'Deck';

Expand All @@ -20,19 +30,44 @@ export default class Deck extends Component {
controls: PropTypes.bool,
globalStyles: PropTypes.bool,
history: PropTypes.object,
onStateChange: PropTypes.func,
progress: PropTypes.oneOf(['pacman', 'bar', 'number', 'none']),
theme: PropTypes.object,
transition: PropTypes.array,
transitionDuration: PropTypes.number
};

static defaultProps = {
onStateChange: defaultOnStateChange
};

state = {
slideState: undefined
};

componentWillUnmount() {
// Cleanup default onStateChange
if (this.state.slideState && !this.props.onStateChange) {
document.documentElement.classList.remove(this.state.slideState);
}
}

handleStateChange = nextState => {
const prevState = this.state.slideState;
if (prevState !== nextState) {
this.props.onStateChange(prevState, nextState);
this.setState({ slideState: nextState });
}
};

render() {
return (
<Provider store={store}>
<Controller
theme={this.props.theme}
store={store}
history={this.props.history}
onStateChange={this.handleStateChange}
>
<Manager {...this.props}>{this.props.children}</Manager>
</Controller>
Expand Down
75 changes: 75 additions & 0 deletions src/components/deck.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react';
import { mount } from 'enzyme';
import Deck, { defaultOnStateChange } from './deck';
import Slide from './slide';
import Text from './text';

describe('<Deck />', () => {
let wrapper;

afterEach(() => {
if (wrapper && wrapper.unmount) {
wrapper.unmount();
wrapper = null;
}
});

test('should call onStateChange prop, with <Slide />s state prop', () => {
const onStateChangeSpy = jest.fn();

wrapper = mount(
<Deck onStateChange={onStateChangeSpy}>
<Slide state="slide-1">
<Text>Test slide</Text>
</Slide>
<Slide state="slide-2">
<Text>Test slide</Text>
</Slide>
</Deck>
);

expect(onStateChangeSpy).toHaveBeenCalledTimes(1);
expect(onStateChangeSpy).lastCalledWith(
undefined, // previous state
'slide-1' // next state
);
});

test('should call onStateChange prop, with previous state and <Slide />s state prop as the next state', () => {
const onStateChangeSpy = jest.fn();

wrapper = mount(
<Deck onStateChange={onStateChangeSpy}>
<Slide state="slide-1">
<Text>Test slide</Text>
</Slide>
<Slide state="slide-2">
<Text>Test slide</Text>
</Slide>
</Deck>
);

expect(onStateChangeSpy).lastCalledWith(
undefined, // previous state
'slide-1' // next state
);

// Go to the next slide
wrapper.find('[aria-label="Next slide"]').simulate('click');

expect(onStateChangeSpy).toHaveBeenCalledTimes(2);
expect(onStateChangeSpy).lastCalledWith(
'slide-1', // previous state
'slide-2' // next state
);
});

test('default onStateChange implementation adds the current state as a class to the document root', () => {
defaultOnStateChange(undefined, 'slide-1');
expect(document.documentElement.classList.contains('slide-1')).toBe(true);

defaultOnStateChange('slide-1', 'slide-2');
expect(document.documentElement.classList.contains('slide-1')).toBe(false);
expect(document.documentElement.classList.contains('slide-2')).toBe(true);
});
});
12 changes: 8 additions & 4 deletions src/components/slide.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class Slide extends React.PureComponent {
});
}

this.context.onStateChange(this.props.state);

if (isFunction(this.props.onActive)) {
this.props.onActive(this.props.slideIndex);
}
Expand Down Expand Up @@ -155,6 +157,7 @@ Slide.propTypes = {
print: PropTypes.bool,
slideIndex: PropTypes.number,
slideReference: PropTypes.array,
state: PropTypes.string,
style: PropTypes.object,
transition: PropTypes.array,
transitionDuration: PropTypes.number,
Expand All @@ -164,13 +167,14 @@ Slide.propTypes = {
};

Slide.contextTypes = {
styles: PropTypes.object,
contentWidth: PropTypes.number,
contentHeight: PropTypes.number,
contentWidth: PropTypes.number,
export: PropTypes.bool,
print: PropTypes.object,
onStateChange: PropTypes.func.isRequired,
overview: PropTypes.bool,
store: PropTypes.object
print: PropTypes.object,
store: PropTypes.object,
styles: PropTypes.object
};

Slide.childContextTypes = {
Expand Down
18 changes: 17 additions & 1 deletion src/components/slide.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { mount } from 'enzyme';
import Slide from './slide';
import Appear from './appear';
import Text from './text';

const _mockContext = function() {
return {
Expand All @@ -19,7 +20,8 @@ const _mockContext = function() {
getState: () => ({ route: { params: '', slide: 0 } }),
subscribe: () => {},
dispatch: () => {}
}
},
onStateChange: () => {}
};
};

Expand Down Expand Up @@ -173,4 +175,18 @@ describe('<Slide />', () => {
]
]);
});

test.only('should call `onStateChange` on mount', () => {
const onStateChangeSpy = jest.fn();
const context = { ..._mockContext(), onStateChange: onStateChangeSpy };
mount(
<Slide state="slide-1">
<Text>Test slide</Text>
</Slide>,
{ context }
);

expect(onStateChangeSpy).toHaveBeenCalledTimes(1);
expect(onStateChangeSpy).lastCalledWith('slide-1');
});
});
11 changes: 7 additions & 4 deletions src/utils/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ class Context extends Component {
static propTypes = {
children: PropTypes.node,
history: PropTypes.object,
onStateChange: PropTypes.func,
store: PropTypes.object,
styles: PropTypes.object
};
static childContextTypes = {
styles: PropTypes.object,
history: PropTypes.object,
onStateChange: PropTypes.func,
styles: PropTypes.object,
store: PropTypes.object
};
getChildContext() {
const { history, styles, store } = this.props;
const { history, onStateChange, styles, store } = this.props;
return {
history,
styles,
store
onStateChange,
store,
styles
};
}
render() {
Expand Down
4 changes: 3 additions & 1 deletion src/utils/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default class Controller extends Component {
static propTypes = {
children: PropTypes.node,
history: PropTypes.object,
onStateChange: PropTypes.func.isRequired,
store: PropTypes.object,
theme: PropTypes.object
};
Expand Down Expand Up @@ -69,8 +70,9 @@ export default class Controller extends Component {

return (
<Context
store={this.props.store}
history={this.history}
onStateChange={this.props.onStateChange}
store={this.props.store}
styles={this.state.print ? styles.print : styles.screen}
>
{this.props.children}
Expand Down