mirror of
https://github.com/jxnblk/mdx-deck.git
synced 2024-11-26 00:35:02 +03:00
Split components and add support for inline modules
This commit is contained in:
parent
7869814b76
commit
b0b00eeb5f
@ -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!
|
||||
|
@ -2,6 +2,6 @@ export default {
|
||||
colors: {
|
||||
text: '#fff',
|
||||
background: '#444',
|
||||
blue: '#07c',
|
||||
link: '#07c',
|
||||
},
|
||||
}
|
||||
|
163
lib/SlideDeck.js
Normal file
163
lib/SlideDeck.js
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
161
lib/entry.js
161
lib/entry.js
@ -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()
|
||||
|
@ -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)}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user