diff --git a/.gitignore b/.gitignore index 00e9e16..034c864 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ dist -site +public coverage node_modules package-lock.json diff --git a/packages/components/notes.md b/packages/components/notes.md new file mode 100644 index 0000000..2323ee7 --- /dev/null +++ b/packages/components/notes.md @@ -0,0 +1,34 @@ +# notes + +mdx-deck v2 prototype + +todo: + +- [x] Head +- [x] Image +- [x] Notes +- [x] Appear +- [ ] Code (may handle this with theming) +- [ ] normalize slide styles with v1 +- [x] history api fallback +- [x] mdx components +- [x] themes +- [ ] layouts +- [ ] presenter mode + - [ ] timer/clock + - [ ] open new tab button + - [ ] look at google slides behavior +- [ ] overview mode + - [ ] highlighted state bug + - [ ] slide numbers +- [ ] localStorage +- [ ] keyboard shortcuts +- [ ] docs for customizing any component +- [x] swipeable + +extras + +- [ ] docs for syntax highlighting +- [ ] Print view +- [ ] PDF export? +- [ ] dots?? diff --git a/packages/components/package.json b/packages/components/package.json index c1e9636..2983331 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,7 +1,7 @@ { "name": "@mdx-deck/components", "version": "2.0.0-0", - "main": "index.js", + "main": "src/index.js", "author": "Brent Jackson ", "license": "MIT" } diff --git a/packages/components/src/Appear.js b/packages/components/src/Appear.js new file mode 100644 index 0000000..03a2c43 --- /dev/null +++ b/packages/components/src/Appear.js @@ -0,0 +1,26 @@ +import React from 'react' +import Steps from './Steps' + +export const Appear = props => { + const arr = React.Children.toArray(props.children) + return ( + { + const children = arr.map((child, i) => + i < step + ? child + : React.cloneElement(child, { + style: { + ...child.props.style, + visibility: 'hidden', + }, + }) + ) + return <>{children} + }} + /> + ) +} + +export default Appear diff --git a/packages/components/src/Head.js b/packages/components/src/Head.js new file mode 100644 index 0000000..9251703 --- /dev/null +++ b/packages/components/src/Head.js @@ -0,0 +1,68 @@ +import React, { useContext } from 'react' +import { createPortal } from 'react-dom' + +export const HeadContext = React.createContext({ + tags: [], + push: () => { + console.warn('Missing HeadProvider') + }, +}) + +export const HeadProvider = ({ tags = [], children }) => { + const push = elements => { + tags.push(...elements) + } + const context = { push } + return {children} +} + +export class Head extends React.Component { + state = { + didMount: false, + } + rehydrate = () => { + const children = React.Children.toArray(this.props.children) + const nodes = [...document.head.querySelectorAll('[data-head]')] + nodes.forEach(node => { + node.remove() + }) + children.forEach(child => { + if (child.type === 'title') { + const title = document.head.querySelector('title') + if (title) title.remove() + } + if (child.type === 'meta') { + const { name } = child.props + let meta + if (name) meta = document.head.querySelector(`meta[name="${name}"]`) + if (meta) meta.remove() + } + }) + this.setState({ didMount: true }) + } + + componentDidMount() { + this.rehydrate() + } + + render() { + const children = React.Children.toArray(this.props.children).map(child => + React.cloneElement(child, { + 'data-head': true, + }) + ) + if (!this.state.didMount) { + return ( + { + push(children) + return false + }} + /> + ) + } + return createPortal(children, document.head) + } +} + +export default Head diff --git a/packages/components/src/Image.js b/packages/components/src/Image.js new file mode 100644 index 0000000..da9a04b --- /dev/null +++ b/packages/components/src/Image.js @@ -0,0 +1,22 @@ +import styled from 'styled-components' + +export const Image = styled.div( + { + backgroundPosition: 'center', + backgroundRepeat: 'no-repeat', + }, + props => ({ + backgroundSize: props.size, + width: props.width, + height: props.height, + backgroundImage: `url(${props.src})`, + }) +) + +Image.defaultProps = { + size: 'cover', + width: '100vw', + height: '100vh', +} + +export default Image diff --git a/packages/components/src/MDXDeck.js b/packages/components/src/MDXDeck.js new file mode 100644 index 0000000..71f6e98 --- /dev/null +++ b/packages/components/src/MDXDeck.js @@ -0,0 +1,196 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { Router, globalHistory, navigate, Link } from '@reach/router' +import { Swipeable } from 'react-swipeable' +import Provider from './Provider' +import Root from './Root' +import Slide from './Slide' +import { default as defaultTheme } from '@mdx-deck/themes' + +const NORMAL = 'NORMAL' +const PRESENTER = 'PRESENTER' +const OVERVIEW = 'OVERVIEW' +const PRINT = 'PRINT' + +const keys = { + right: 39, + left: 37, + space: 32, + p: 80, + o: 79, +} + +const toggleMode = key => state => ({ + mode: state.mode === key ? NORMAL : key, +}) + +const BaseWrapper = props => <>{props.children} + +export class MDXDeck extends React.Component { + constructor(props) { + super(props) + + this.state = { + slides: props.slides, + step: 0, + mode: NORMAL, + } + } + + handleKeyDown = e => { + const { key, keyCode, metaKey, ctrlKey, altKey, shiftKey } = e + const { activeElement } = document + if (activeElement.tagName !== 'BODY' && activeElement.tagName !== 'DIV') + return + if (metaKey || ctrlKey) return + const alt = altKey && !shiftKey + const shift = shiftKey && !altKey + + if (alt) { + switch (keyCode) { + case keys.p: + this.setState(toggleMode(PRESENTER)) + break + case keys.o: + this.setState(toggleMode(OVERVIEW)) + break + } + } else { + switch (keyCode) { + case keys.left: + e.preventDefault() + this.previous() + break + case keys.right: + case keys.space: + e.preventDefault() + this.next() + break + } + } + } + + getIndex = () => { + const { pathname } = globalHistory.location + return Number(pathname.split('/')[1] || 0) + } + + getMeta = i => { + const { slides } = this.state + const { meta = {} } = slides[i] || {} + return meta + } + + goto = i => { + const current = this.getIndex() + const reverse = i < current + navigate('/' + i) + const meta = this.getMeta(i) + this.setState({ + step: reverse ? meta.steps || 0 : 0, + }) + } + + previous = () => { + const { slides, step } = this.state + const index = this.getIndex() + const meta = this.getMeta(index) + if (meta.steps && step > 0) { + this.setState(state => ({ + step: state.step - 1, + })) + } else { + const previous = index - 1 + if (previous < 0) return + this.goto(previous) + } + } + + next = () => { + const { slides, step } = this.state + const index = this.getIndex() + const meta = this.getMeta(index) + if (meta.steps && step < meta.steps) { + this.setState(state => ({ + step: state.step + 1, + })) + } else { + const next = index + 1 + if (next > slides.length - 1) return + this.goto(next) + } + } + + register = (index, meta) => { + const { slides } = this.state + const initialMeta = slides[index].meta || {} + slides[index].meta = { ...initialMeta, ...meta } + this.setState({ slides }) + } + + componentDidMount() { + document.body.addEventListener('keydown', this.handleKeyDown) + } + + componentWillUnmount() { + document.body.removeEventListener('keydown', this.handleKeyDown) + } + + render() { + const { slides, mode } = this.state + const index = this.getIndex() + const meta = this.getMeta(index) + const context = { + ...this.state, + register: this.register, + } + + const [FirstSlide] = slides + + let Wrapper = BaseWrapper + switch (mode) { + case PRESENTER: + Wrapper = Presenter + break + case OVERVIEW: + Wrapper = Overview + break + } + + return ( + + + + + {/**/} + + + + + {slides.map((Component, i) => ( + + + + ))} + + + + + + ) + } +} + +MDXDeck.propTypes = { + slides: PropTypes.array.isRequired, + theme: PropTypes.object.isRequired, + headTags: PropTypes.array.isRequired, +} + +MDXDeck.defaultProps = { + slides: [], + theme: defaultTheme, + headTags: [], +} + +export default MDXDeck diff --git a/packages/components/src/Notes.js b/packages/components/src/Notes.js new file mode 100644 index 0000000..9b08094 --- /dev/null +++ b/packages/components/src/Notes.js @@ -0,0 +1,19 @@ +import React from 'react' +import { withContext } from './context' + +export const Notes = withContext( + class extends React.Component { + constructor(props) { + super(props) + const { context, children } = props + if (!context || typeof context.index === 'undefined') return + context.register(context.index, { notes: children }) + } + + render() { + return false + } + } +) + +export default Notes diff --git a/packages/components/src/Overview.js b/packages/components/src/Overview.js index 8d1024c..a86be51 100644 --- a/packages/components/src/Overview.js +++ b/packages/components/src/Overview.js @@ -1,92 +1,65 @@ import React from 'react' -import PropTypes from 'prop-types' -import get from 'lodash.get' -import Box from './Box' -import Flex from './Flex' +import { Link } from '@reach/router' import Zoom from './Zoom' import Slide from './Slide' -import Root from './Root' -import Mono from './Mono' -export const Overview = ({ - index, - length, - slides = [], - mode, - metadata = {}, - update, - step, - ...props -}) => { - const notes = get(metadata, index + '.notes') +const noop = () => {} + +export const Overview = props => { + const { index, slides } = props return ( - - {slides.map((Component, i) => ( - { - update({ index: i }) + outline: i === index ? '4px solid #0cf' : null, }} > - - - - - + + + - + ))} - - - - {props.children} - - - - {index + 1}/{length} - - - {notes} - - + +
+ {props.children} +
+ ) } -Overview.propTypes = { - index: PropTypes.number.isRequired, - length: PropTypes.number.isRequired, - update: PropTypes.func.isRequired, - step: PropTypes.number.isRequired, - slides: PropTypes.array, - mode: PropTypes.string, - notes: PropTypes.object, -} - export default Overview diff --git a/packages/components/src/Presenter.js b/packages/components/src/Presenter.js index 991490b..073bd2f 100644 --- a/packages/components/src/Presenter.js +++ b/packages/components/src/Presenter.js @@ -1,112 +1,68 @@ import React from 'react' -import PropTypes from 'prop-types' -import get from 'lodash.get' -import Box from './Box' -import Flex from './Flex' import Zoom from './Zoom' import Slide from './Slide' -import Root from './Root' -import Timer from './Timer' -import Mono from './Mono' -import Button from './Button' -const Anchor = Button.withComponent('a') +const noop = () => {} -export const Presenter = ({ - index, - length, - slides = [], - mode, - metadata = {}, - update, - step, - ...props -}) => { +export const Presenter = props => { + const { slides, index } = props + const Current = slides[index] const Next = slides[index + 1] - const notes = get(metadata, index + '.notes') + const { notes } = Current.meta || {} return ( - - - +
- - {props.children} + {props.children} +
+
+ + {Next && ( + + + + )} - - - - - - {Next && ( - - - - )} - - - - - {notes} - - - - - - Slide {index + 1} of {length} - - - - Open in Normal mode - - - - - + {notes} +
+ +
+
+          {index + 1} of {slides.length}
+        
+
+ ) } - -Presenter.propTypes = { - index: PropTypes.number.isRequired, - length: PropTypes.number.isRequired, - update: PropTypes.func.isRequired, - step: PropTypes.number.isRequired, - slides: PropTypes.array, - mode: PropTypes.string, - metadata: PropTypes.object, -} - -export default Presenter diff --git a/packages/components/src/Provider.js b/packages/components/src/Provider.js new file mode 100644 index 0000000..0a0c476 --- /dev/null +++ b/packages/components/src/Provider.js @@ -0,0 +1,32 @@ +import React from 'react' +import { HeadProvider } from './Head' +import { ThemeProvider } from 'styled-components' +import { MDXProvider } from '@mdx-js/tag' +import mdxComponents from './mdx-components' + +const DefaultProvider = props => <>{props.children} + +export const Provider = props => { + const { headTags, theme, components } = props + const { + Provider: UserProvider = DefaultProvider, + components: themeComponents = {}, + } = theme + + const allComponents = { + ...mdxComponents, + ...themeComponents, + } + + return ( + + + + + + + + ) +} + +export default Provider diff --git a/packages/components/src/Root.js b/packages/components/src/Root.js new file mode 100644 index 0000000..32d3753 --- /dev/null +++ b/packages/components/src/Root.js @@ -0,0 +1,31 @@ +import styled from 'styled-components' + +const themed = (...tags) => props => + tags.map(tag => props.theme[tag] && { ['& ' + tag]: props.theme[tag] }) + +export const Root = styled.div( + props => ({ + fontFamily: props.theme.font, + color: props.theme.colors.text, + backgroundColor: props.theme.colors.background, + }), + props => props.theme.css, + themed( + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'a', + 'ul', + 'ol', + 'li', + 'p', + 'blockquote', + 'img', + 'table' + ) +) + +export default Root diff --git a/packages/components/src/Slide.js b/packages/components/src/Slide.js new file mode 100644 index 0000000..f3b8a38 --- /dev/null +++ b/packages/components/src/Slide.js @@ -0,0 +1,23 @@ +import React from 'react' +import styled from 'styled-components' +import { Context } from './context' + +const SlideRoot = styled.div( + { + display: 'flex', + flexDirection: 'column', + width: '100vw', + height: '100vh', + alignItems: 'center', + justifyContent: 'center', + }, + props => props.theme.Slide +) + +export const Slide = ({ children, ...props }) => ( + + {children} + +) + +export default Slide diff --git a/packages/components/src/Steps.js b/packages/components/src/Steps.js new file mode 100644 index 0000000..c36b79e --- /dev/null +++ b/packages/components/src/Steps.js @@ -0,0 +1,20 @@ +import React from 'react' +import { withContext } from './context' + +export const Steps = withContext( + class extends React.Component { + constructor(props) { + super(props) + const { register, index } = props.context + const { length } = props + register(index, { steps: length }) + } + render() { + const { context, render } = this.props + const { step } = context + return render({ step }) + } + } +) + +export default Steps diff --git a/packages/components/src/Zoom.js b/packages/components/src/Zoom.js index fd7694f..4ff4035 100644 --- a/packages/components/src/Zoom.js +++ b/packages/components/src/Zoom.js @@ -1,41 +1,23 @@ import React from 'react' -import PropTypes from 'prop-types' import styled from 'styled-components' -const ZoomRoot = styled.div( - [], - { - backgroundColor: 'white', - }, - props => ({ - width: 100 * props.zoom + 'vw', - height: 100 * props.zoom + 'vh', - }) -) - -ZoomRoot.propTypes = { - zoom: PropTypes.number.isRequired, -} +const ZoomRoot = styled.div(props => ({ + backgroundColor: props.theme.colors.background, + width: 100 * props.zoom + 'vw', + height: 100 * props.zoom + 'vh', +})) const ZoomInner = styled.div([], props => ({ transformOrigin: '0 0', transform: `scale(${props.zoom})`, })) -ZoomInner.propTypes = { - zoom: PropTypes.number.isRequired, -} - -const Zoom = ({ zoom, ...props }) => ( +export const Zoom = ({ zoom, ...props }) => ( ) -Zoom.propTypes = { - zoom: PropTypes.number, -} - Zoom.defaultProps = { zoom: 1, } diff --git a/packages/components/src/Button.js b/packages/components/src/_ref/Button.js similarity index 100% rename from packages/components/src/Button.js rename to packages/components/src/_ref/Button.js diff --git a/packages/components/src/_ref/Overview.js b/packages/components/src/_ref/Overview.js new file mode 100644 index 0000000..8d1024c --- /dev/null +++ b/packages/components/src/_ref/Overview.js @@ -0,0 +1,92 @@ +import React from 'react' +import PropTypes from 'prop-types' +import get from 'lodash.get' +import Box from './Box' +import Flex from './Flex' +import Zoom from './Zoom' +import Slide from './Slide' +import Root from './Root' +import Mono from './Mono' + +export const Overview = ({ + index, + length, + slides = [], + mode, + metadata = {}, + update, + step, + ...props +}) => { + const notes = get(metadata, index + '.notes') + + return ( + + + {slides.map((Component, i) => ( + { + update({ index: i }) + }} + > + + + + + + + + + ))} + + + + {props.children} + + + + {index + 1}/{length} + + + {notes} + + + ) +} + +Overview.propTypes = { + index: PropTypes.number.isRequired, + length: PropTypes.number.isRequired, + update: PropTypes.func.isRequired, + step: PropTypes.number.isRequired, + slides: PropTypes.array, + mode: PropTypes.string, + notes: PropTypes.object, +} + +export default Overview diff --git a/packages/components/src/_ref/Presenter.js b/packages/components/src/_ref/Presenter.js new file mode 100644 index 0000000..991490b --- /dev/null +++ b/packages/components/src/_ref/Presenter.js @@ -0,0 +1,112 @@ +import React from 'react' +import PropTypes from 'prop-types' +import get from 'lodash.get' +import Box from './Box' +import Flex from './Flex' +import Zoom from './Zoom' +import Slide from './Slide' +import Root from './Root' +import Timer from './Timer' +import Mono from './Mono' +import Button from './Button' + +const Anchor = Button.withComponent('a') + +export const Presenter = ({ + index, + length, + slides = [], + mode, + metadata = {}, + update, + step, + ...props +}) => { + const Next = slides[index + 1] + const notes = get(metadata, index + '.notes') + + return ( + + + + + {props.children} + + + + + + + {Next && ( + + + + )} + + + + + {notes} + + + + + + Slide {index + 1} of {length} + + + + Open in Normal mode + + + + + + ) +} + +Presenter.propTypes = { + index: PropTypes.number.isRequired, + length: PropTypes.number.isRequired, + update: PropTypes.func.isRequired, + step: PropTypes.number.isRequired, + slides: PropTypes.array, + mode: PropTypes.string, + metadata: PropTypes.object, +} + +export default Presenter diff --git a/packages/components/src/SlideDeck.js b/packages/components/src/_ref/SlideDeck.js similarity index 100% rename from packages/components/src/SlideDeck.js rename to packages/components/src/_ref/SlideDeck.js diff --git a/packages/components/src/Timer.js b/packages/components/src/_ref/Timer.js similarity index 100% rename from packages/components/src/Timer.js rename to packages/components/src/_ref/Timer.js diff --git a/packages/components/src/constants.js b/packages/components/src/_ref/constants.js similarity index 100% rename from packages/components/src/constants.js rename to packages/components/src/_ref/constants.js diff --git a/packages/components/src/_temp.js b/packages/components/src/_temp.js new file mode 100644 index 0000000..50cf103 --- /dev/null +++ b/packages/components/src/_temp.js @@ -0,0 +1,8 @@ +// may cut some of this + +const GoogleFonts = withTheme( + props => + !!props.theme.googleFont && ( + + ) +) diff --git a/packages/components/src/context.js b/packages/components/src/context.js new file mode 100644 index 0000000..aeffc2c --- /dev/null +++ b/packages/components/src/context.js @@ -0,0 +1,11 @@ +import React, { useContext } from 'react' + +export const Context = React.createContext(null) + +export const withContext = Component => props => ( + } + /> +) + +export const useDeck = () => useContext(Context) diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 36db02f..48f5119 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -1,641 +1,7 @@ -/* - * mdx-deck v2 prototype - * - * todo: - * - [x] Head - * - [x] Image - * - [x] Notes - * - [x] Appear - * - [ ] Code (may handle this with theming) - * - [x] history api fallback - * - [x] mdx components - * - [x] themes - * - [ ] layouts - * - [ ] presenter mode - * - [ ] timer/clock - * - [ ] open new tab button - * - [ ] look at google slides behavior - * - [ ] overview mode - * - [ ] highlighted state bug - * - [ ] slide numbers - * - [ ] localStorage - * - [ ] keyboard shortcuts - * - [ ] docs for customizing any component - * - [x] swipeable - * - * extras - * - [ ] docs for syntax highlighting - * - [ ] Print view - * - [ ] PDF export? - * - [ ] dots?? - */ - -import React from 'react' -import { createPortal } from 'react-dom' -import PropTypes from 'prop-types' -import { Router, globalHistory, navigate, Link } from '@reach/router' -import styled, { ThemeProvider, withTheme } from 'styled-components' -import { MDXProvider } from '@mdx-js/tag' -import { Swipeable } from 'react-swipeable' -import { default as defaultTheme } from './themes' - -const NORMAL = 'NORMAL' -const PRESENTER = 'PRESENTER' -const OVERVIEW = 'OVERVIEW' -const PRINT = 'PRINT' - -export const Context = React.createContext(null) - -export const withContext = Component => props => ( - } - /> -) - -// TODO check against v1 styles -const SlideRoot = styled.div( - { - display: 'flex', - flexDirection: 'column', - width: '100vw', - height: '100vh', - alignItems: 'center', - justifyContent: 'center', - }, - props => props.theme.Slide -) - -const Slide = ({ children, ...props }) => ( - - {children} - -) - -const DefaultProvider = props => <>{props.children} - -// MDX components -const Heading = styled.h1({ margin: 0 }) - -const inlineCode = styled.code( - props => ({ - fontFamily: props.theme.monospace, - }), - props => props.theme.code -) - -const code = styled.pre( - props => ({ - fontFamily: props.theme.monospace, - fontSize: '.75em', - }), - props => props.theme.pre -) - -const img = styled.img({ - maxWidth: '100%', - height: 'auto', - objectFit: 'cover', -}) - -const TableWrap = styled.div({ - overflowX: 'auto', -}) -const Table = styled.table({ - width: '100%', - borderCollapse: 'separate', - borderSpacing: 0, - '& td, & th': { - textAlign: 'left', - paddingRight: '.5em', - paddingTop: '.25em', - paddingBottom: '.25em', - borderBottom: '1px solid', - verticalAlign: 'top', - }, -}) -const table = props => ( - - - -) - -const components = { - pre: props => props.children, - code, - inlineCode, - img, - table, -} - -const keys = { - right: 39, - left: 37, - space: 32, - p: 80, - o: 79, -} - -const toggleMode = key => state => ({ - mode: state.mode === key ? NORMAL : key, -}) - -const ZoomOuter = styled.div(props => ({ - backgroundColor: props.theme.colors.background, - width: 100 * props.zoom + 'vw', - height: 100 * props.zoom + 'vh', -})) -const ZoomInner = styled.div(props => ({ - transformOrigin: '0 0', - transform: `scale(${props.zoom})`, -})) -const Zoom = props => ( - - - -) -Zoom.defaultProps = { - zoom: 1, -} - -const noop = () => {} - -const Presenter = props => { - const { slides, index } = props - const Current = slides[index] - const Next = slides[index + 1] - const { notes } = Current.meta || {} - - return ( -
-
-
- {props.children} -
-
- - {Next && ( - - - - )} - - {notes} -
-
-
-
-          {index + 1} of {slides.length}
-        
-
-
- ) -} - -const Overview = props => { - const { index, slides } = props - - return ( -
-
- {slides.map((Component, i) => ( - - - - - - - - ))} -
-
- {props.children} -
-
- ) -} - -const Root = props => <>{props.children} - -const themed = (...tags) => props => - tags.map(tag => props.theme[tag] && { ['& ' + tag]: props.theme[tag] }) - -const RootStyles = styled.div( - props => ({ - fontFamily: props.theme.font, - color: props.theme.colors.text, - backgroundColor: props.theme.colors.background, - }), - props => props.theme.css, - themed( - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'a', - 'ul', - 'ol', - 'li', - 'p', - 'blockquote', - 'img', - 'table' - ) -) - -const GoogleFonts = withTheme( - props => - !!props.theme.googleFont && ( - - ) -) - -export class MDXDeck extends React.Component { - constructor(props) { - super(props) - - this.state = { - slides: props.slides, - step: 0, - mode: NORMAL, - } - } - - handleKeyDown = e => { - const { key, keyCode, metaKey, ctrlKey, altKey, shiftKey } = e - const { activeElement } = document - if (activeElement.tagName !== 'BODY' && activeElement.tagName !== 'DIV') - return - if (metaKey || ctrlKey) return - const alt = altKey && !shiftKey - const shift = shiftKey && !altKey - - if (alt) { - switch (keyCode) { - case keys.p: - this.setState(toggleMode(PRESENTER)) - break - case keys.o: - this.setState(toggleMode(OVERVIEW)) - break - } - } else { - switch (keyCode) { - case keys.left: - e.preventDefault() - this.previous() - break - case keys.right: - case keys.space: - e.preventDefault() - this.next() - break - } - } - } - - getIndex = () => { - const { pathname } = globalHistory.location - return Number(pathname.split('/')[1] || 0) - } - - getMeta = i => { - const { slides } = this.state - const { meta = {} } = slides[i] || {} - return meta - } - - goto = i => { - const current = this.getIndex() - const reverse = i < current - navigate('/' + i) - const meta = this.getMeta(i) - this.setState({ - step: reverse ? meta.steps || 0 : 0, - }) - } - - previous = () => { - const { slides, step } = this.state - const index = this.getIndex() - const meta = this.getMeta(index) - if (meta.steps && step > 0) { - this.setState(state => ({ - step: state.step - 1, - })) - } else { - const previous = index - 1 - if (previous < 0) return - this.goto(previous) - } - } - - next = () => { - const { slides, step } = this.state - const index = this.getIndex() - const meta = this.getMeta(index) - if (meta.steps && step < meta.steps) { - this.setState(state => ({ - step: state.step + 1, - })) - } else { - const next = index + 1 - if (next > slides.length - 1) return - this.goto(next) - } - } - - register = (index, meta) => { - const { slides } = this.state - const initialMeta = slides[index].meta || {} - slides[index].meta = { ...initialMeta, ...meta } - this.setState({ slides }) - } - - componentDidMount() { - document.body.addEventListener('keydown', this.handleKeyDown) - } - - componentWillUnmount() { - document.body.removeEventListener('keydown', this.handleKeyDown) - } - - render() { - const { headTags, theme, components } = this.props - const { slides, mode } = this.state - const index = this.getIndex() - const meta = this.getMeta(index) - const context = { - ...this.state, - register: this.register, - } - const { - Provider = DefaultProvider, - components: themeComponents = {}, - } = theme - - const [FirstSlide] = slides - - const mdxComponents = { - ...components, - ...themeComponents, - } - - let Wrapper = Root - switch (mode) { - case PRESENTER: - Wrapper = Presenter - break - case OVERVIEW: - Wrapper = Overview - break - } - - return ( - - - - - - - - - - - - - {slides.map((Component, i) => ( - - - - ))} - - - - - - - - - ) - } -} - -MDXDeck.propTypes = { - slides: PropTypes.array.isRequired, - theme: PropTypes.object.isRequired, - components: PropTypes.object, - // Provider: PropTypes.func, - headTags: PropTypes.array.isRequired, -} -MDXDeck.defaultProps = { - slides: [], - theme: defaultTheme, - headTags: [], - components, -} - -const HeadContext = React.createContext({ - tags: [], - push: () => { - console.warn('Missing HeadProvider') - }, -}) - -const HeadProvider = ({ tags = [], children }) => { - const push = elements => { - tags.push(...elements) - } - const context = { push } - return {children} -} - -export class Head extends React.Component { - state = { - didMount: false, - } - rehydrate = () => { - const children = React.Children.toArray(this.props.children) - const nodes = [...document.head.querySelectorAll('[data-head]')] - nodes.forEach(node => { - node.remove() - }) - children.forEach(child => { - if (child.type === 'title') { - const title = document.head.querySelector('title') - if (title) title.remove() - } - if (child.type === 'meta') { - const { name } = child.props - let meta - if (name) meta = document.head.querySelector(`meta[name="${name}"]`) - if (meta) meta.remove() - } - }) - this.setState({ didMount: true }) - } - - componentDidMount() { - this.rehydrate() - } - - render() { - const children = React.Children.toArray(this.props.children).map(child => - React.cloneElement(child, { - 'data-head': true, - }) - ) - if (!this.state.didMount) { - return ( - { - push(children) - return false - }} - /> - ) - } - return createPortal(children, document.head) - } -} - -export const Image = styled.div( - { - backgroundPosition: 'center', - backgroundRepeat: 'no-repeat', - }, - props => ({ - backgroundSize: props.size, - width: props.width, - height: props.height, - backgroundImage: `url(${props.src})`, - }) -) - -Image.defaultProps = { - size: 'cover', - width: '100vw', - height: '100vh', -} - -export const Notes = withContext( - class extends React.Component { - constructor(props) { - super(props) - const { context, children } = props - if (!context || typeof context.index === 'undefined') return - context.register(context.index, { notes: children }) - } - - render() { - return false - } - } -) - -export const Steps = withContext( - class extends React.Component { - constructor(props) { - super(props) - const { register, index } = props.context - const { length } = props - register(index, { steps: length }) - } - render() { - const { context, render } = this.props - const { step } = context - return render({ step }) - } - } -) - -export const Appear = props => { - const arr = React.Children.toArray(props.children) - return ( - { - const children = arr.map((child, i) => - i < step - ? child - : React.cloneElement(child, { - style: { - ...child.props.style, - visibility: 'hidden', - }, - }) - ) - return <>{children} - }} - /> - ) -} +export { MDXDeck } from './MDXDeck' +export { Head } from './Head' +export { Image } from './Image' +export { Notes } from './Notes' +export { Steps } from './Steps' +export { Appear } from './Appear' +export { withContext, useDeck } from './context' diff --git a/packages/components/src/mdx-components.js b/packages/components/src/mdx-components.js new file mode 100644 index 0000000..e648d56 --- /dev/null +++ b/packages/components/src/mdx-components.js @@ -0,0 +1,58 @@ +import React from 'react' +import styled from 'styled-components' + +export const Heading = styled.h1({ margin: 0 }) + +export const inlineCode = styled.code( + props => ({ + fontFamily: props.theme.monospace, + }), + props => props.theme.code +) + +export const code = styled.pre( + props => ({ + fontFamily: props.theme.monospace, + fontSize: '.75em', + }), + props => props.theme.pre +) + +export const img = styled.img({ + maxWidth: '100%', + height: 'auto', + objectFit: 'cover', +}) + +export const TableWrap = styled.div({ + overflowX: 'auto', +}) +export const Table = styled.table({ + width: '100%', + borderCollapse: 'separate', + borderSpacing: 0, + '& td, & th': { + textAlign: 'left', + paddingRight: '.5em', + paddingTop: '.25em', + paddingBottom: '.25em', + borderBottom: '1px solid', + verticalAlign: 'top', + }, +}) + +export const table = props => ( + +
+ +) + +export const components = { + pre: props => props.children, + code, + inlineCode, + img, + table, +} + +export default components diff --git a/packages/html-plugin/README.md b/packages/html-plugin/README.md new file mode 100644 index 0000000..e878be5 --- /dev/null +++ b/packages/html-plugin/README.md @@ -0,0 +1,16 @@ +# @mdx-deck/webpack-html-plugin + +Webpack plugin for generating HTML + +```sh +npm i @mdx-deck/webpack-html-plugin +``` + +```js +// webpack.config.js +const HTMLPlugin = require('@mdx-deck/webpack-html-plugin') + +module.exports = { + plugins: [new HTMLPlugin()], +} +``` diff --git a/packages/mdx-deck/lib/html-plugin.js b/packages/html-plugin/index.js similarity index 100% rename from packages/mdx-deck/lib/html-plugin.js rename to packages/html-plugin/index.js diff --git a/packages/html-plugin/package.json b/packages/html-plugin/package.json index b1e77c1..93b541c 100644 --- a/packages/html-plugin/package.json +++ b/packages/html-plugin/package.json @@ -3,5 +3,8 @@ "version": "2.0.0-0", "author": "Brent Jackson ", "license": "MIT", - "repository": "github:jxnblk/mdx-deck" + "repository": "github:jxnblk/mdx-deck", + "dependencies": { + "webpack-sources": "^1.3.0" + } } diff --git a/packages/layouts/package.json b/packages/layouts/package.json new file mode 100644 index 0000000..9993792 --- /dev/null +++ b/packages/layouts/package.json @@ -0,0 +1,7 @@ +{ + "name": "@mdx-deck/layouts", + "version": "2.0.0-0", + "main": "index.js", + "author": "Brent Jackson ", + "license": "MIT" +} diff --git a/packages/mdx-deck/lib/loader.js b/packages/loader/index.js similarity index 67% rename from packages/mdx-deck/lib/loader.js rename to packages/loader/index.js index 634b092..e7e06c0 100644 --- a/packages/mdx-deck/lib/loader.js +++ b/packages/loader/index.js @@ -1,14 +1,12 @@ const { getOptions } = require('loader-utils') const mdx = require('@mdx-js/mdx') -// const normalizeNewline = require('normalize-newline') -const mdxPluginSplit = require('mdx-plugin-split') +const mdxPlugin = require('@mdx-deck/mdx-plugin') module.exports = async function(src) { const callback = this.async() const options = getOptions(this) || {} - // options.skipExport = true options.mdPlugins = options.mdPlugins || [] - options.mdPlugins.push(mdxPluginSplit) + options.mdPlugins.push(mdxPlugin) const result = mdx.sync(src, options) @@ -16,7 +14,5 @@ module.exports = async function(src) { import { MDXTag } from '@mdx-js/tag' ${result}` - console.log(code) - return callback(null, code) } diff --git a/packages/loader/package.json b/packages/loader/package.json new file mode 100644 index 0000000..95c1191 --- /dev/null +++ b/packages/loader/package.json @@ -0,0 +1,12 @@ +{ + "name": "@mdx-deck/loader", + "version": "2.0.0-0", + "main": "index.js", + "author": "Brent Jackson ", + "license": "MIT", + "dependencies": { + "@mdx-deck/mdx-plugin": "^2.0.0-0", + "@mdx-js/mdx": "^0.20.0", + "loader-utils": "^1.2.3" + } +} diff --git a/packages/mdx-deck/demo.mdx b/packages/mdx-deck/demo.mdx new file mode 100644 index 0000000..27370be --- /dev/null +++ b/packages/mdx-deck/demo.mdx @@ -0,0 +1,94 @@ +import { Head, Image, Notes, Appear, Steps } from '@mdx-deck/components' + + + MDX Deck Demo + + +# Hello MDX Deck + +--- + +## This is v2 + +--- + +## What's New + +
    + +
  • Reach Router
  • +
  • Less opinionated styles
  • +
  • more stuff
  • +
    +
+ +--- + + + +--- + +## This slide has notes + + + Hello, secret speaker notes + + +--- + +```js +const codeExample = require('./code-example') +``` + +--- + +## More Appear + + +
One
+
Two
+
Three
+
Four
+
Five
+
Six
+
+ +--- + +## Steps Components + + ( +
+      Step: {step}
+    
+ )} +/> + +--- + +export default props => +
+ {props.children} +
+ +## With a (tomato) layout + +--- + +## Last Slide + + diff --git a/packages/mdx-deck/lib/config.js b/packages/mdx-deck/lib/config.js index e8731f8..30c0ff1 100644 --- a/packages/mdx-deck/lib/config.js +++ b/packages/mdx-deck/lib/config.js @@ -9,7 +9,7 @@ const remark = { emoji: require('remark-emoji'), unwrapImages: require('remark-unwrap-images'), } -const HTMLPlugin = require('./html-plugin') +const HTMLPlugin = require('@mdx-deck/webpack-html-plugin') const babel = require('../babel.config') const rules = [ @@ -35,7 +35,7 @@ const rules = [ options: babel, }, { - loader: require.resolve('./loader.js'), + loader: require.resolve('@mdx-deck/loader'), options: { mdPlugins: [remark.emoji, remark.unwrapImages], }, @@ -80,7 +80,7 @@ const createConfig = (opts = {}) => { path.join(opts.dirname, 'node_modules') ) - config.entry = [path.join(__dirname, '../src/entry.js')] + config.entry = [path.join(__dirname, './entry.js')] const defs = Object.assign({}, opts.globals, { OPTIONS: JSON.stringify(opts), diff --git a/packages/components/src/entry.js b/packages/mdx-deck/lib/entry.js similarity index 51% rename from packages/components/src/entry.js rename to packages/mdx-deck/lib/entry.js index 92561d2..b99e120 100644 --- a/packages/components/src/entry.js +++ b/packages/mdx-deck/lib/entry.js @@ -1,23 +1,13 @@ import React from 'react' import { render } from 'react-dom' -import { MDXDeck } from './index' +import { MDXDeck } from '@mdx-deck/components' const mod = require(FILENAME) -const { slides } = mod -const { theme, components, Provider } = mod - -console.log(slides) +const { slides, theme } = mod export default class App extends React.Component { render() { - return ( - - ) + return } } diff --git a/packages/mdx-deck/package.json b/packages/mdx-deck/package.json index 562a10e..88068c3 100644 --- a/packages/mdx-deck/package.json +++ b/packages/mdx-deck/package.json @@ -2,13 +2,12 @@ "name": "mdx-deck", "version": "1.10.0", "description": "MDX-based presentation decks", - "main": "src/index.js", "bin": { "mdx-deck": "./cli.js" }, "scripts": { - "start": "./cli.js docs/index.mdx -p 8080", - "build": "./cli.js build docs/index.mdx -d site", + "start": "./cli.js demo.mdx -p 8080", + "build": "./cli.js build demo.mdx -d public", "help": "./cli.js", "test": "jest" }, @@ -24,6 +23,9 @@ "@babel/plugin-syntax-dynamic-import": "^7.0.0", "@babel/preset-env": "^7.3.4", "@babel/preset-react": "^7.0.0", + "@mdx-deck/components": "^2.0.0-0", + "@mdx-deck/loader": "^2.0.0-0", + "@mdx-deck/webpack-html-plugin": "^2.0.0-0", "@mdx-js/mdx": "^0.20.0", "@mdx-js/tag": "^0.18.0", "@reach/router": "^1.2.1", @@ -60,7 +62,6 @@ "webpack-hot-middleware": "^2.24.3", "webpack-merge": "^4.2.1", "webpack-node-externals": "^1.7.2", - "webpack-sources": "^1.3.0", "webpackbar": "^3.1.5" }, "devDependencies": { diff --git a/packages/mdx-plugin/package.json b/packages/mdx-plugin/package.json index 47a3fc4..aed11a0 100644 --- a/packages/mdx-plugin/package.json +++ b/packages/mdx-plugin/package.json @@ -1,6 +1,6 @@ { - "name": "mdx-plugin-split", - "version": "0.0.2", + "name": "@mdx-deck/mdx-plugin", + "version": "2.0.0-0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/themes/package.json b/packages/themes/package.json new file mode 100644 index 0000000..21881e4 --- /dev/null +++ b/packages/themes/package.json @@ -0,0 +1,7 @@ +{ + "name": "@mdx-deck/themes", + "version": "2.0.0-0", + "main": "src/index.js", + "author": "Brent Jackson ", + "license": "MIT" +} diff --git a/yarn.lock b/yarn.lock index 4a04500..17efc57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5362,6 +5362,14 @@ mdx-deck-code-surfer@^0.5.5: code-surfer "^0.5.5" memoize-one "^4.0.2" +mdx-plugin-split@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/mdx-plugin-split/-/mdx-plugin-split-0.0.2.tgz#608d90f12108593badfc54ce24533ef82fd41d47" + integrity sha512-Kl8H0CVO4QPJ1AZ+Weaf7uHMB+px4uffZ2h3tl7ItcvU4VJkASRjeFYJN5GtQbmM8RpXTgxyp+xfaQqHOClNXQ== + dependencies: + unist-util-is "^2.1.2" + unist-util-visit "^1.4.0" + mem@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/mem/-/mem-4.1.0.tgz#aeb9be2d21f47e78af29e4ac5978e8afa2ca5b8a"