mirror of
https://github.com/jxnblk/mdx-deck.git
synced 2024-11-29 13:58:02 +03:00
commit
b6c0119243
27
README.md
27
README.md
@ -262,6 +262,33 @@ To use presenter mode:
|
||||
- Display the other window on the screen for the audience to see.
|
||||
- Control the presentation from your window by using the left and right arrow keys; the other window should stay in sync
|
||||
|
||||
### Speaker Notes
|
||||
|
||||
Notes that only show in presenter mode can be added to any slide.
|
||||
Speaker notes can be added in one of the following two ways:
|
||||
|
||||
**Markdown:** Use the `notes` language attribute in a fenced code block to add speaker notes.
|
||||
|
||||
````mdx
|
||||
# Slide Content
|
||||
|
||||
```notes
|
||||
These are only visible in presenter mode
|
||||
```
|
||||
````
|
||||
|
||||
**Notes Component:** Use the `Notes` component to create more complex speaker notes.
|
||||
|
||||
````mdx
|
||||
import { Notes } from 'mdx-deck'
|
||||
|
||||
# Slide Content
|
||||
|
||||
<Notes>
|
||||
Only visible in presenter mode
|
||||
</Notes>
|
||||
````
|
||||
|
||||
## Exporting
|
||||
|
||||
Add a `build` script to your `package.json` to export a presentation as HTML with a JS bundle.
|
||||
|
@ -1,5 +1,5 @@
|
||||
export { future as theme } from '../src/themes'
|
||||
import { Image } from '../src'
|
||||
import { Image, Notes } from '../src'
|
||||
import Layout from './Layout'
|
||||
|
||||
# mdx-deck
|
||||
@ -33,14 +33,28 @@ import Box from 'superbox'
|
||||
3. Present
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
<button>code example</button>
|
||||
```
|
||||
|
||||
```notes
|
||||
- These are speaker notes
|
||||
- And they won't rendered in your slide
|
||||
```
|
||||
|
||||
---
|
||||
> “Blockquotes are essential to any good presentation”
|
||||
|
||||
– Anonymous
|
||||
|
||||
<Notes>
|
||||
<ul>
|
||||
<li>Speaker notes can also be</li>
|
||||
<li>Written in JSX</li>
|
||||
</ul>
|
||||
</Notes>
|
||||
|
||||
---
|
||||
|
||||
<Image src='https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20' />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import styled from 'styled-components'
|
||||
import { space, color } from 'styled-system'
|
||||
import { space, width, color } from 'styled-system'
|
||||
|
||||
const Flex = styled.div([], {
|
||||
display: 'flex',
|
||||
@ -7,6 +7,10 @@ const Flex = styled.div([], {
|
||||
'@media print': {
|
||||
display: 'none'
|
||||
}
|
||||
}, props => props.css, space, color)
|
||||
}, props => props.css,
|
||||
space,
|
||||
width,
|
||||
color
|
||||
)
|
||||
|
||||
export default Flex
|
||||
|
@ -5,6 +5,7 @@ export default props =>
|
||||
<Box
|
||||
{...props}
|
||||
css={{
|
||||
fontFamily: 'Menlo, monospace'
|
||||
fontFamily: 'Menlo, monospace',
|
||||
whiteSpace: 'pre-wrap'
|
||||
}}
|
||||
/>
|
||||
|
22
src/Notes.js
Normal file
22
src/Notes.js
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
import { withDeck } from './context'
|
||||
import { withSlide } from './Slide'
|
||||
|
||||
export default withDeck(withSlide(class extends React.Component {
|
||||
setNotes = (props) => {
|
||||
const { slide, deck, children } = props
|
||||
if (!slide.index) return
|
||||
deck.addNotes({
|
||||
index: slide.index,
|
||||
children
|
||||
})
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.setNotes(this.props)
|
||||
}
|
||||
|
||||
render () {
|
||||
return false
|
||||
}
|
||||
}))
|
@ -12,6 +12,7 @@ export const Presenter = ({
|
||||
length,
|
||||
slides = [],
|
||||
mode,
|
||||
notes = {},
|
||||
...props
|
||||
}) => {
|
||||
const Next = slides[index + 1]
|
||||
@ -24,11 +25,10 @@ export const Presenter = ({
|
||||
height: '100vh'
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
my='auto'
|
||||
css={{ alignItems: 'flex-start' }}>
|
||||
<Flex my='auto'>
|
||||
<Box
|
||||
mx='auto'
|
||||
width={5/8}
|
||||
css={{
|
||||
border: '1px solid rgba(128, 128, 128, 0.25)'
|
||||
}}>
|
||||
@ -38,21 +38,36 @@ export const Presenter = ({
|
||||
</Root>
|
||||
</Zoom>
|
||||
</Box>
|
||||
<Box
|
||||
<Flex
|
||||
width={1/4}
|
||||
mx='auto'
|
||||
css={{
|
||||
border: '1px solid rgba(128, 128, 128, 0.25)'
|
||||
flex: 'none',
|
||||
flexDirection: 'column'
|
||||
}}>
|
||||
<Zoom zoom={1/4}>
|
||||
<Root {...props}>
|
||||
{Next && (
|
||||
<Slide>
|
||||
<Next />
|
||||
</Slide>
|
||||
)}
|
||||
</Root>
|
||||
</Zoom>
|
||||
</Box>
|
||||
<Box
|
||||
mx='auto'
|
||||
css={{
|
||||
border: '1px solid rgba(128, 128, 128, 0.25)'
|
||||
}}>
|
||||
<Zoom zoom={1/4}>
|
||||
<Root {...props}>
|
||||
{Next && (
|
||||
<Slide>
|
||||
<Next />
|
||||
</Slide>
|
||||
)}
|
||||
</Root>
|
||||
</Zoom>
|
||||
</Box>
|
||||
<Box
|
||||
py={3}
|
||||
css={{
|
||||
flex: 'auto'
|
||||
}}>
|
||||
{notes[index]}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex mt='auto' px={3} py={3}>
|
||||
<Mono>Slide {index} of {length}</Mono>
|
||||
|
29
src/Slide.js
29
src/Slide.js
@ -1,7 +1,20 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { space, color } from 'styled-system'
|
||||
|
||||
export const Slide = styled.div([], {
|
||||
const Context = React.createContext(null)
|
||||
|
||||
export const withSlide = Component => props =>
|
||||
<Context.Consumer>
|
||||
{slide => (
|
||||
<Component
|
||||
{...props}
|
||||
slide={slide}
|
||||
/>
|
||||
)}
|
||||
</Context.Consumer>
|
||||
|
||||
const Root = styled.div([], {
|
||||
flex: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@ -19,6 +32,20 @@ export const Slide = styled.div([], {
|
||||
}
|
||||
}, space, color)
|
||||
|
||||
class Slide extends React.Component {
|
||||
render () {
|
||||
const {
|
||||
index,
|
||||
...props
|
||||
} = this.props
|
||||
return (
|
||||
<Context.Provider value={{ index }}>
|
||||
<Root {...props} />
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Slide.defaultProps = {
|
||||
px: [ 4, 5, 6 ]
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import {
|
||||
space,
|
||||
color
|
||||
} from 'styled-system'
|
||||
import Notes from './Notes'
|
||||
import Mono from './Mono'
|
||||
|
||||
const css = key => props => props.theme[key]
|
||||
|
||||
@ -89,15 +91,16 @@ blockquote.defaultProps = {
|
||||
color: 'quote'
|
||||
}
|
||||
|
||||
const pre = styled.pre([], props => ({
|
||||
fontFamily: props.theme.monospace
|
||||
const Pre = styled.pre([], props => ({
|
||||
fontFamily: props.theme.monospace,
|
||||
whiteSpace: 'pre-wrap'
|
||||
}),
|
||||
fontSize,
|
||||
space,
|
||||
color,
|
||||
css('pre')
|
||||
)
|
||||
pre.defaultProps = {
|
||||
Pre.defaultProps = {
|
||||
fontSize: 1,
|
||||
m: 0,
|
||||
p: 2,
|
||||
@ -105,10 +108,23 @@ pre.defaultProps = {
|
||||
bg: 'preBackground'
|
||||
}
|
||||
|
||||
const code = styled.code([], props => ({
|
||||
const code = props => {
|
||||
switch (props.className) {
|
||||
case 'language-notes':
|
||||
return (
|
||||
<Notes>
|
||||
<Mono {...props} color='white' />
|
||||
</Notes>
|
||||
)
|
||||
default:
|
||||
return <Pre {...props} />
|
||||
}
|
||||
}
|
||||
|
||||
const inlineCode = styled.code([], props => ({
|
||||
fontFamily: props.theme.monospace
|
||||
}), fontSize, space, color, css('code'))
|
||||
code.defaultProps = {
|
||||
inlineCode.defaultProps = {
|
||||
color: 'code',
|
||||
bg: 'codeBackground'
|
||||
}
|
||||
@ -132,7 +148,7 @@ export default {
|
||||
ol,
|
||||
li,
|
||||
pre: props => props.children,
|
||||
code: pre,
|
||||
inlineCode: code,
|
||||
code,
|
||||
inlineCode,
|
||||
img
|
||||
}
|
||||
|
16
src/context.js
Normal file
16
src/context.js
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
|
||||
export const Context = React.createContext(null)
|
||||
export const { Provider, Consumer } = Context
|
||||
|
||||
export const withDeck = Component => props =>
|
||||
<Consumer>
|
||||
{deck => (
|
||||
<Component
|
||||
{...props}
|
||||
deck={deck}
|
||||
/>
|
||||
)}
|
||||
</Consumer>
|
||||
|
||||
export default Context
|
93
src/index.js
93
src/index.js
@ -4,6 +4,7 @@ import { MDXProvider } from '@mdx-js/tag'
|
||||
import { ThemeProvider } from 'styled-components'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import { Provider } from './context'
|
||||
import Carousel from './Carousel'
|
||||
import Slide from './Slide'
|
||||
import Dots from './Dots'
|
||||
@ -15,6 +16,7 @@ import defaultTheme from './themes'
|
||||
import defaultComponents from './components'
|
||||
|
||||
export { default as Image } from './Image'
|
||||
export { default as Notes } from './Notes'
|
||||
export { default as components } from './components'
|
||||
|
||||
// themes
|
||||
@ -49,7 +51,8 @@ export class SlideDeck extends React.Component {
|
||||
state = {
|
||||
length: this.props.slides.length,
|
||||
index: 0,
|
||||
mode: modes.normal
|
||||
mode: modes.normal,
|
||||
notes: {}
|
||||
}
|
||||
|
||||
update = fn => this.setState(fn)
|
||||
@ -104,6 +107,15 @@ export class SlideDeck extends React.Component {
|
||||
this.setState({ index })
|
||||
}
|
||||
|
||||
addNotes = ({ index, children }) => {
|
||||
this.setState(state => ({
|
||||
notes: {
|
||||
...state.notes,
|
||||
[index]: children
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
document.body.addEventListener('keydown', this.handleKeyDown)
|
||||
window.addEventListener('hashchange', this.handleHashChange)
|
||||
@ -123,8 +135,10 @@ export class SlideDeck extends React.Component {
|
||||
this.isHashChange = false
|
||||
return
|
||||
}
|
||||
const { index } = this.state
|
||||
history.pushState(null, null, '#' + index)
|
||||
const { index, mode } = this.state
|
||||
let query = '?'
|
||||
if (mode === modes.presenter) query += 'presenter'
|
||||
history.pushState(null, null, query + '#' + index)
|
||||
localStorage.setItem('mdx-slide', index)
|
||||
}
|
||||
|
||||
@ -142,38 +156,49 @@ export class SlideDeck extends React.Component {
|
||||
? Presenter
|
||||
: Root
|
||||
|
||||
const context = {
|
||||
...this.state,
|
||||
slides,
|
||||
addNotes: this.addNotes
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<MDXProvider
|
||||
components={{
|
||||
...defaultComponents,
|
||||
...components
|
||||
}}>
|
||||
<Wrapper
|
||||
{...this.state}
|
||||
slides={slides}
|
||||
width={width}
|
||||
height={height}>
|
||||
<GoogleFonts />
|
||||
<Carousel index={index}>
|
||||
{slides.map((Component, i) => (
|
||||
<Slide key={i} id={'slide-' + i}>
|
||||
<Component />
|
||||
</Slide>
|
||||
))}
|
||||
</Carousel>
|
||||
<Dots
|
||||
mt={-32}
|
||||
mx='auto'
|
||||
index={index}
|
||||
length={length}
|
||||
onClick={index => {
|
||||
this.setState({ index })
|
||||
}}
|
||||
/>
|
||||
</Wrapper>
|
||||
</MDXProvider>
|
||||
</ThemeProvider>
|
||||
<Provider value={context}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<MDXProvider
|
||||
components={{
|
||||
...defaultComponents,
|
||||
...components
|
||||
}}>
|
||||
<Wrapper
|
||||
{...this.state}
|
||||
slides={slides}
|
||||
width={width}
|
||||
height={height}>
|
||||
<GoogleFonts />
|
||||
<Carousel index={index}>
|
||||
{slides.map((Component, i) => (
|
||||
<Slide
|
||||
key={i}
|
||||
id={'slide-' + i}
|
||||
index={i}>
|
||||
<Component />
|
||||
</Slide>
|
||||
))}
|
||||
</Carousel>
|
||||
<Dots
|
||||
mt={-32}
|
||||
mx='auto'
|
||||
index={index}
|
||||
length={length}
|
||||
onClick={index => {
|
||||
this.setState({ index })
|
||||
}}
|
||||
/>
|
||||
</Wrapper>
|
||||
</MDXProvider>
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ exports[`renders blockquote 1`] = `
|
||||
|
||||
exports[`renders code 1`] = `
|
||||
.c0 {
|
||||
white-space: pre-wrap;
|
||||
font-size: 14px;
|
||||
margin: 0px;
|
||||
padding: 8px;
|
||||
|
Loading…
Reference in New Issue
Block a user