1
1
mirror of https://github.com/jxnblk/mdx-deck.git synced 2024-11-29 13:58:02 +03:00

Split components and add support for inline modules

This commit is contained in:
Brent Jackson 2018-07-28 18:13:50 -04:00
parent 7869814b76
commit b0b00eeb5f
6 changed files with 202 additions and 150 deletions

View File

@ -1,8 +1,4 @@
---
modules:
- import Box from 'superbox'
---
`- export { default as theme } from './theme'`
export { default as theme } from './theme'
# Hello
@ -17,6 +13,7 @@ modules:
[MDX]: https://github.com/mdx-js/mdx
---
import Box from 'superbox'
<Box p={3} bg='magenta'>
And you can import React components!

View File

@ -2,6 +2,6 @@ export default {
colors: {
text: '#fff',
background: '#444',
blue: '#07c',
link: '#07c',
},
}

163
lib/SlideDeck.js Normal file
View File

@ -0,0 +1,163 @@
import React from 'react'
import PropTypes from 'prop-types'
import { MDXProvider } from '@mdx-js/tag'
import styled, { ThemeProvider } from 'styled-components'
import { color } from 'styled-system'
import debounce from 'lodash.debounce'
import defaultTheme from './theme'
import defaultComponents from './components'
export const inc = state => ({ index: (state.index + 1) % state.length })
export const dec = state => state.index > 0
? ({ index: (state.index - 1) % state.length })
: null
const CarouselRoot = styled.div([], {
display: 'flex',
overflowX: 'auto',
width: '100vw',
scrollSnapType: 'mandatory',
'::-webkit-scrollbar': {
display: 'none'
}
})
export class Carousel extends React.Component {
root = React.createRef()
isScroll = false
handleScroll = debounce(e => {
if (this.isScroll) {
this.isScroll = false
return
}
const { scrollLeft } = e.target
const rect = e.target.getBoundingClientRect()
const n = Math.round(scrollLeft / rect.width)
this.props.onScroll(n)
}, 200)
scrollTo = (index) => {
if (!this.root.current) return
const el = this.root.current.querySelector('#slide-' + index)
if (!el) return
el.scrollIntoView({ behavior: 'smooth' })
}
componentDidMount () {
this.root.current.addEventListener('scroll', this.handleScroll)
}
componentWillUnmount () {
this.root.current.removeEventListener('scroll', this.handleScroll)
}
componentDidUpdate (prev) {
if (prev.index === this.props.index) return
this.isScroll = true
this.scrollTo(this.props.index)
}
render () {
const { onScroll, index, ...props } = this.props
return (
<CarouselRoot
innerRef={this.root}
{...props}
/>
)
}
}
export const Slide = styled.div([], {
flex: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
overflow: 'hidden',
width: '100vw',
height: '90vh'
}, color)
Slide.defaultProps = {
color: 'text',
bg: 'background'
}
export default class SlideDeck extends React.Component {
static propTypes = {
slides: PropTypes.array.isRequired,
}
static defaultProps = {
slides: [],
theme: defaultTheme,
components: defaultComponents
}
state = {
length: this.props.slides.length,
index: 0
}
update = fn => this.setState(fn)
handleKeyDown = e => {
console.log(e)
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return
switch (e.key) {
case 'ArrowRight':
case ' ':
e.preventDefault()
this.update(inc)
break
case 'ArrowLeft':
e.preventDefault()
this.update(dec)
break
}
}
componentDidMount () {
document.body.addEventListener('keydown', this.handleKeyDown)
}
componentWillUnmount () {
document.body.removeEventListener('keydown', this.handleKeyDown)
}
render () {
const {
slides,
theme,
components
} = this.props
const { index } = this.state
return (
<ThemeProvider theme={theme}>
<MDXProvider components={components}>
<div>
<Carousel
index={index}
onScroll={index => {
this.setState({ index })
}}>
{slides.map((Component, i) => (
<Slide key={i} id={'slide-' + i}>
<Component />
</Slide>
))}
</Carousel>
<samp>{this.state.index + 1}/{this.state.length}</samp>
<button onClick={e => this.update(dec)}>previous</button>
<button onClick={e => this.update(inc)}>next</button>
</div>
</MDXProvider>
</ThemeProvider>
)
}
}

View File

@ -31,7 +31,13 @@ const h2 = props =>
fontSize={[ 4, 5, 6 ]}
/>
const a = styled.a([], {}, color)
a.defaultProps = {
color: 'link'
}
export default {
h1,
h2
h2,
a
}

View File

@ -1,14 +1,7 @@
import React from 'react'
import { render } from 'react-dom'
import PropTypes from 'prop-types'
import { MDXProvider } from '@mdx-js/tag'
import styled, { ThemeProvider } from 'styled-components'
import { color } from 'styled-system'
import Box from 'superbox'
import throttle from 'lodash.throttle'
import debounce from 'lodash.debounce'
import defaultTheme from './theme'
import defaultComponents from './components'
import SlideDeck from './SlideDeck'
// todo: dynamic import
import slides, {
@ -17,148 +10,32 @@ import slides, {
meta
} from '../docs/index.mdx'
const CarouselRoot = styled.div([], {
display: 'flex',
overflowX: 'auto',
width: '100vw',
scrollSnapType: 'mandatory',
'::-webkit-scrollbar': {
display: 'none'
}
})
class Carousel extends React.Component {
root = React.createRef()
isScroll = false
handleScroll = debounce(e => {
if (this.isScroll) {
this.isScroll = false
return
}
const { scrollLeft } = e.target
const rect = e.target.getBoundingClientRect()
const n = Math.round(scrollLeft / rect.width)
this.props.onScroll(n)
}, 200)
scrollTo = (index) => {
if (!this.root.current) return
const el = this.root.current.querySelector('#slide-' + index)
if (!el) return
el.scrollIntoView({ behavior: 'smooth' })
}
componentDidMount () {
this.root.current.addEventListener('scroll', this.handleScroll)
}
componentWillUnmount () {
this.root.current.removeEventListener('scroll', this.handleScroll)
}
componentDidUpdate (prev) {
if (prev.index === this.props.index) return
this.isScroll = true
this.scrollTo(this.props.index)
}
class App extends React.Component {
render () {
const { onScroll, index, ...props } = this.props
const {
slides,
theme,
components
} = this.props
return (
<CarouselRoot
innerRef={this.root}
{...props}
<SlideDeck
slides={slides}
theme={theme}
components={components}
/>
)
}
}
const Slide = styled.div([], {
// outline: '2px solid tomato',
flex: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
overflow: 'hidden',
width: '100vw',
height: '90vh'
}, color)
Slide.defaultProps = {
color: 'text',
bg: 'background'
}
const inc = state => ({ index: (state.index + 1) % state.length })
const dec = state => state.index > 0
? ({ index: (state.index - 1) % state.length })
: null
class App extends React.Component {
static propTypes = {
slides: PropTypes.array.isRequired,
}
state = {
length: this.props.slides.length,
index: 0
}
update = fn => this.setState(fn)
handleKeyDown = e => {
switch (e.key) {
case 'ArrowRight':
e.preventDefault()
this.update(inc)
break
case 'ArrowLeft':
e.preventDefault()
this.update(dec)
break
}
}
componentDidMount () {
document.body.addEventListener('keydown', this.handleKeyDown)
}
componentWillUnmount () {
document.body.removeEventListener('keydown', this.handleKeyDown)
}
render () {
const { slides } = this.props
const { index } = this.state
return (
<ThemeProvider theme={theme || defaultTheme}>
<MDXProvider components={components || defaultComponents}>
<div>
<Carousel
index={index}
onScroll={index => {
this.setState({ index })
}}>
{slides.map((Component, i) => (
<Slide key={i} id={'slide-' + i}>
<Component />
</Slide>
))}
</Carousel>
<samp>{this.state.index + 1}/{this.state.length}</samp>
<button onClick={e => this.update(dec)}>previous</button>
<button onClick={e => this.update(inc)}>next</button>
</div>
</MDXProvider>
</ThemeProvider>
)
}
}
render(<App slides={slides} />, window.root)
render(
<App
slides={slides}
theme={theme}
components={components}
/>,
window.root
)
if (module.hot) module.hot.accept()

View File

@ -3,18 +3,26 @@ const matter = require('gray-matter')
const stringifyObject = require('stringify-object')
const EXREG = /export\sdefault\s/g
const MODREG = /^(import|export)\s/
module.exports = async function (src) {
const callback = this.async()
const { data, content } = matter(src)
const inlineModules = []
const slides = content.split('---\n')
.map(str => mdx.sync(str))
.map(str => str.trim())
.map(str => str.replace(EXREG, ''))
.map(str => {
const lines = str.split('\n\n')
inlineModules.push(
...lines.filter(str => MODREG.test(str))
)
return lines.filter(str => !MODREG.test(str))
})
console.log(data)
const {
modules = []
} = data
@ -22,6 +30,7 @@ module.exports = async function (src) {
const code = `import React from 'react'
import { MDXTag } from '@mdx-js/tag'
${modules.join('\n')}
${inlineModules.join('\n')}
export const meta = ${stringifyObject(data)}