mirror of
https://github.com/jxnblk/mdx-deck.git
synced 2024-11-25 07:05:52 +03:00
commit
f9bc2456ff
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@ public
|
||||
coverage
|
||||
node_modules
|
||||
package-lock.json
|
||||
public
|
||||
.cache
|
||||
|
22
MIGRATION.md
22
MIGRATION.md
@ -1,5 +1,27 @@
|
||||
# Migration
|
||||
|
||||
## Updating to MDX Deck v3
|
||||
|
||||
- The `export default` syntax for slide layouts is no longer supported. Replace this syntax with the layout component wrapped around the slide content instead.
|
||||
- The following packages have been deprecated. Import components directly from the `mdx-deck` package instead.
|
||||
- `@mdx-deck/components`
|
||||
- `@mdx-deck/layouts`
|
||||
- `@mdx-deck/mdx-plugin`
|
||||
- `@mdx-deck/loader`
|
||||
- `@mdx-deck/webpack-html-plugin`
|
||||
- The Gatsby theme package as been renamed: `gatsby-theme-mdx-deck`
|
||||
- Theming now uses [Theme UI][], and the theme format has changed.
|
||||
- See the [theming docs](/docs/theming.md) for information on creating custom themes.
|
||||
- **Or** use the `convertLegacyTheme` utility to shim themes written in the v2 format
|
||||
- The standalone CLI has been rewritten with Gatsby, and the following CLI flags are no longer supported:
|
||||
- `--webpack` - use the Gatsby theme directly to customize webpack features
|
||||
- `--out-dir` - decks are now built in the `public/` directory
|
||||
- `--no-html` - individual slides are rendered client side, but the first slide is always rendered as static HTML using Gatsby
|
||||
- Custom `Presenter` components can no longer be added to a theme. Use the component shadowing API with the Gatsby theme instead.
|
||||
- Multiple MDX files can no longer be combined into a single presentation
|
||||
|
||||
[theme ui]: https://theme-ui.com
|
||||
|
||||
## Updating to MDX Deck v2
|
||||
|
||||
With a few exceptions, decks created with v1 should be compatible with v2. The following is a list of steps to ensure your slide deck will work with v2.
|
||||
|
@ -1 +0,0 @@
|
||||
module.exports = require('./packages/mdx-deck/babel.config')
|
3
cypress.json
Normal file
3
cypress.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:8000/"
|
||||
}
|
21
cypress/integration/hello.js
Normal file
21
cypress/integration/hello.js
Normal file
@ -0,0 +1,21 @@
|
||||
context('MDX Deck', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('http://localhost:8000')
|
||||
})
|
||||
|
||||
it('opens', () => {
|
||||
cy.visit('http://localhost:8000')
|
||||
})
|
||||
|
||||
it('contains the title', () => {
|
||||
cy.contains('MDX Deck')
|
||||
})
|
||||
|
||||
/* doesn't work
|
||||
it('goes to the next slide', () => {
|
||||
cy.get('body')
|
||||
.type('{rightarrow}')
|
||||
.contains('Presentation')
|
||||
})
|
||||
*/
|
||||
})
|
17
cypress/plugins/index.js
Normal file
17
cypress/plugins/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
25
cypress/support/commands.js
Normal file
25
cypress/support/commands.js
Normal file
@ -0,0 +1,25 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
20
cypress/support/index.js
Normal file
20
cypress/support/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
146
docs/advanced.md
146
docs/advanced.md
@ -1,154 +1,36 @@
|
||||
# Advanced Usage
|
||||
|
||||
## Custom MDX components
|
||||
|
||||
MDX Deck includes default components for MDX, but to provide custom components to the [MDXProvider][], add a `components` object to the `theme`.
|
||||
|
||||
```js
|
||||
// example theme
|
||||
import Heading from './Heading'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
h1: Heading,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
See the [MDX][] docs for more or take a look
|
||||
at the [default set of components](../packages/components/src/mdx-components.js) as a reference.
|
||||
|
||||
## Custom Provider component
|
||||
|
||||
A custom Provider component is useful for adding custom context providers in React or adding persistent UI elements to the entire deck.
|
||||
|
||||
To define a custom Provider component, you'll need to import it into your custom theme and set it using the key `Provider` like shown below:
|
||||
To define a custom Provider component, you'll need to import it and add it as the `Provider` key in your theme.
|
||||
|
||||
```js
|
||||
// example theme.js
|
||||
import Provider from './Provider'
|
||||
|
||||
export default {
|
||||
font: 'Georgia, serif',
|
||||
fonts: {
|
||||
body: 'Georgia, serif',
|
||||
},
|
||||
Provider,
|
||||
}
|
||||
```
|
||||
|
||||
A custom Provider component will receive the application's state as props,
|
||||
which can be used to show custom page numbers or add other elements to the UI.
|
||||
|
||||
#### Props
|
||||
|
||||
- `slides`: (array) the components for each slide
|
||||
- `index`: (number) the current slide index
|
||||
- `mode`: (string) the current mode (one of `'normal'`, `'presenter'`, or `'overview'`)
|
||||
- `step`: (number) the current visible step in an Appear or Step component
|
||||
- Each slide includes a `meta` object with a `notes` field when the Notes component is used within a slide
|
||||
|
||||
#### Example
|
||||
|
||||
The example below will display the current slide out of the total amount of slides.
|
||||
Use the `useDeck` hook to get the presentation state in your custom `Provider` component.
|
||||
|
||||
```js
|
||||
// Example Provider.js
|
||||
// example Provider.js
|
||||
import React from 'react'
|
||||
import { useDeck } from 'mdx-deck'
|
||||
|
||||
function AtTheBottomCenter ({ children }) {
|
||||
const css = {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
color: #ffffff;
|
||||
textAlign: 'center',
|
||||
}
|
||||
export default props => {
|
||||
const state = useDeck()
|
||||
|
||||
return <div css={css}>
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
|
||||
export function Provider ({ children, ...props }) {
|
||||
return <>
|
||||
{children}
|
||||
<AtTheBottomCenter>{props.index}/{props.slides.length}</AtTheBottomCenter>
|
||||
</>
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Combining multiple mdx files
|
||||
|
||||
Unlike the official `@mdx-js/loader`,
|
||||
the `@mdx-deck/loader` exports an additional `slides` array of components instead of just the entire document.
|
||||
Multiple MDX files can be combined into a single presentation if the filesize is getting difficult to manage.
|
||||
|
||||
First create a couple `.mdx` files like any other MDX Deck file, with `---` to separate the different slides.
|
||||
|
||||
```mdx
|
||||
# one.mdx
|
||||
|
||||
---
|
||||
|
||||
This is the first file
|
||||
```
|
||||
|
||||
```mdx
|
||||
# two.mdx
|
||||
|
||||
---
|
||||
|
||||
This is the second file
|
||||
```
|
||||
|
||||
Next, create a `.js` file to import and combine the two `.mdx` files.
|
||||
|
||||
```js
|
||||
// deck.js
|
||||
// if you want to include a theme, you would export here:
|
||||
// export { dark as theme } from 'mdx-deck/themes';
|
||||
|
||||
import { slides as one } from './one.mdx'
|
||||
import { slides as two } from './two.mdx'
|
||||
|
||||
export const slides = [...one, ...two]
|
||||
```
|
||||
|
||||
Then, point the MDX Deck CLI comment in your `package.json` to the `deck.js` file.
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"start": "mdx-deck deck.js"
|
||||
}
|
||||
```
|
||||
|
||||
## Custom webpack config
|
||||
|
||||
Webpack configuration files named `webpack.config.js` will automatically be merged with the built-in configuration,
|
||||
using [webpack-merge](https://github.com/survivejs/webpack-merge).
|
||||
To use a custom filename, pass the file path to the `--webpack` flag.
|
||||
|
||||
```js
|
||||
// webpack.config.js example
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [{ loader: 'babel-loader' }, { loader: 'react-svg-loader' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Careful**: When overwriting the loader for `mdx` files, make sure to include the default loader from `@mdx-deck/loader`.
|
||||
|
||||
## Custom Components
|
||||
|
||||
To build custom components that hook into internal MDX Deck state, you might want to use the following APIs:
|
||||
|
||||
- [useSteps](api.md#usesteps-hook)
|
||||
- [useDeck](api.md#usedeck-hook)
|
||||
|
||||
[mdx]: https://mdxjs.com
|
||||
[mdxprovider]: https://github.com/mdx-js/mdx/blob/master/docs/getting-started/index.md#mdxprovider
|
||||
|
107
docs/api.md
107
docs/api.md
@ -1,62 +1,39 @@
|
||||
# API
|
||||
|
||||
MDX Deck consists of several different packages. The core `mdx-deck` package includes the CLI, `@mdx-deck/components`,
|
||||
`@mdx-deck/themes`, and `@mdx-deck/layouts`.
|
||||
The core `mdx-deck` package is a wrapper around the Gatsby CLI with the `gatsby-theme-mdx-deck` package.
|
||||
|
||||
## Components
|
||||
|
||||
See the [components docs](components.md) for details.
|
||||
- `Head`: Adds elements to the document `<head>`
|
||||
- `Notes`: Adds speaker notes to a slide
|
||||
- `Appear`: Steps through child elements one-by-one
|
||||
- `Embed`: Embed MDX Deck slides in other React applications
|
||||
|
||||
- `MDXDeck`
|
||||
- `MDXDeckState`
|
||||
- `Head`
|
||||
- `Image`
|
||||
- `Notes`
|
||||
- `Steps`
|
||||
- `Appear`
|
||||
- `Slide`
|
||||
- `Zoom`
|
||||
- `Embed`
|
||||
See [Components](components.md) for more info.
|
||||
|
||||
## Layouts
|
||||
|
||||
See the [layouts docs](layouts.md) for details.
|
||||
- `Image`: A fullscreen background image layout component
|
||||
- `Invert`: Inverts the foreground and background colors of the slide
|
||||
- `Split`: Renders the first element on the left and other elements to the right
|
||||
- `SplitRight`: Renders the first element on the right and other elements to the left
|
||||
- `Horizontal`: Renders all child elements side-by-side
|
||||
- `FullScreenCode`: Renders a single child code block fullscreen
|
||||
|
||||
- `Invert`
|
||||
- `Split`
|
||||
- `SplitRight`
|
||||
- `FullScreenCode`
|
||||
- `Horizontal`
|
||||
See [Layouts](layouts.md) for more info.
|
||||
|
||||
## Themes
|
||||
## Hooks
|
||||
|
||||
See the [themes](themes.md) & [theming](theming.md) docs for details.
|
||||
### `useSteps`
|
||||
|
||||
## Context
|
||||
|
||||
MDX Deck uses a stateful React context for each slide.
|
||||
Use the context APIs with caution as they are less stable than the end-user MDX Deck API.
|
||||
|
||||
- `metadata` (object) object for storing slide metadata such as speaker notes and step count
|
||||
- `step` (number) the current step index
|
||||
- `mode` (string) the current application mode
|
||||
- `modes` (object) object of application modes
|
||||
- `update` (function) updates application state
|
||||
- `register` (function) registers slide metadata
|
||||
- `index` (number) the current slide index
|
||||
- `goto` (function) function to navigate to a specific slide
|
||||
- `previous` (function) navigate to the previous slide or step
|
||||
- `next` (function) navigate to the next slide or step
|
||||
|
||||
## `useSteps` Hook
|
||||
|
||||
The `useSteps` hook can be used to register custom components that rely on steps, similar to the Appear component.
|
||||
It takes one argument for the total length of steps and returns the current step state.
|
||||
The `useSteps` hook can be used to register custom components that rely on multiple "steps" within a single slide,
|
||||
similar to the Appear component.
|
||||
The hook takes one argument for the total `length` of steps and returns the current `step` state.
|
||||
|
||||
```jsx
|
||||
// example
|
||||
import React from 'react'
|
||||
import { useSteps } from '@mdx-deck/components'
|
||||
import { useSteps } from 'mdx-deck'
|
||||
|
||||
export default props => {
|
||||
const length = 4
|
||||
@ -70,13 +47,22 @@ export default props => {
|
||||
}
|
||||
```
|
||||
|
||||
## `useDeck` Hook
|
||||
## `useDeck`
|
||||
|
||||
The `useDeck` component can be used to hook into MDX Deck state.
|
||||
It returns the [app context](#context) and can be used in a custom [Provider component][] or other custom components.
|
||||
The `useDeck` hook returns the MDX Deck state, including:
|
||||
|
||||
- `setState`
|
||||
- `mode`
|
||||
- `index`
|
||||
- `length`
|
||||
- `step`
|
||||
- `metadata`
|
||||
- `steps`
|
||||
- `notes`
|
||||
- `slug`
|
||||
|
||||
```jsx
|
||||
// example custom Provider
|
||||
// example
|
||||
import React from 'react'
|
||||
import { useDeck } from '@mdx-deck/components'
|
||||
|
||||
@ -86,14 +72,7 @@ export default props => {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
<div
|
||||
css={{
|
||||
position: 'fixed',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
margin: 16,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
Slide {state.index + 1}/{state.length}
|
||||
</div>
|
||||
</div>
|
||||
@ -101,20 +80,10 @@ export default props => {
|
||||
}
|
||||
```
|
||||
|
||||
## `useTheme` Hook
|
||||
## CLI Options
|
||||
|
||||
The `useTheme` hook returns the current [theme](theming.md).
|
||||
|
||||
```jsx
|
||||
// example
|
||||
import React from 'react'
|
||||
import { useTheme } from '@mdx-deck/components'
|
||||
|
||||
export default props => {
|
||||
const theme = useTheme()
|
||||
|
||||
return <h2 style={{ border: `1px solid ${theme.colors.text}` }}>Hello</h2>
|
||||
}
|
||||
```
|
||||
|
||||
[provider component]: advanced.md#custom-provider-component
|
||||
-p --port Dev server port
|
||||
-h --host Host the dev server listens to
|
||||
--no-open Prevent from opening in default browser
|
||||
```
|
||||
|
@ -1,8 +0,0 @@
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
|
||||
.BundleAnalyzerPlugin
|
||||
|
||||
console.log('bundle analyzer')
|
||||
|
||||
module.exports = {
|
||||
plugins: [new BundleAnalyzerPlugin()],
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
# Components
|
||||
|
||||
MDX Deck includes a few built-in components to help with creating presentations.
|
||||
MDX Deck includes components to help with creating presentations.
|
||||
|
||||
## Head
|
||||
|
||||
Use the `<Head />` component to set content in the document head.
|
||||
Use the `Head` component to set content in the document head.
|
||||
|
||||
```mdx
|
||||
// example for twitter cards
|
||||
@ -22,7 +22,7 @@ import { Head } from 'mdx-deck'
|
||||
|
||||
## Image
|
||||
|
||||
Use the `<Image />` component to render a fullscreen image (using the CSS `background-image` property).
|
||||
Use the `Image` component to render a fullscreen image with the CSS `background-image` property.
|
||||
|
||||
```mdx
|
||||
import { Image } from 'mdx-deck'
|
||||
@ -30,117 +30,58 @@ import { Image } from 'mdx-deck'
|
||||
<Image src="kitten.png" />
|
||||
```
|
||||
|
||||
### Props
|
||||
```mdx
|
||||
import { Image } from 'mdx-deck'
|
||||
|
||||
<Image src='kittens.png'>
|
||||
|
||||
# Kittens
|
||||
|
||||
</Image>
|
||||
```
|
||||
|
||||
**Props**
|
||||
|
||||
- `src` (string) image URL
|
||||
- `size` (string) CSS background-size
|
||||
|
||||
## Appear
|
||||
|
||||
Use the `<Appear />` component to make its children appear one at a time within a single slide.
|
||||
Use the `Appear` component to make child elements appear one at a time within a single slide.
|
||||
Use the left and right arrow keys to step through each element.
|
||||
|
||||
```mdx
|
||||
import { Appear } from 'mdx-deck'
|
||||
|
||||
<ul>
|
||||
<li>One</li>
|
||||
<Appear>
|
||||
<li>One</li>
|
||||
<li>Two</li>
|
||||
<li>Three</li>
|
||||
<li>Four</li>
|
||||
</Appear>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Internally, the `<Appear />` component uses the `<Step />` component, which can be used to build custom components with similar behavior.
|
||||
Internally, the `Appear` component uses the `useSteps` hook, which can be used to build custom components with similar behavior.
|
||||
|
||||
## Speaker Notes
|
||||
## Notes
|
||||
|
||||
Speaker notes that only show in presenter mode can be added to any slide with the Notes component.
|
||||
Speaker notes that only show in presenter mode can be added to any slide with the `Notes` component.
|
||||
|
||||
```mdx
|
||||
import { Notes } from 'mdx-deck'
|
||||
|
||||
# Slide Content
|
||||
|
||||
<Notes>Only visible in presenter mode</Notes>
|
||||
<Notes>
|
||||
|
||||
- Only visible in presenter mode
|
||||
- Markdown syntax can be used with empty lines around the content
|
||||
|
||||
</Notes>
|
||||
```
|
||||
|
||||
## Layouts
|
||||
|
||||
MDX Deck includes a few built-in layouts for common slide variations.
|
||||
Export a layout as the `default` within a slide to wrap the contents.
|
||||
|
||||
### Invert
|
||||
|
||||
Inverts the foreground and background colors from the theme.
|
||||
|
||||
```mdx
|
||||
import { Invert } from 'mdx-deck/layouts'
|
||||
|
||||
# Normal
|
||||
|
||||
---
|
||||
|
||||
<Invert>
|
||||
|
||||
# Inverted
|
||||
|
||||
</Invert>
|
||||
```
|
||||
|
||||
### Split
|
||||
|
||||
Creates a horizontal layout with the first child on the left and all other children on the right.
|
||||
|
||||
```mdx
|
||||
import { Split } from 'mdx-deck/layouts'
|
||||
|
||||
<Split>
|
||||
|
||||
![](kitten.png)
|
||||
|
||||
## Meow
|
||||
|
||||
</Split>
|
||||
```
|
||||
|
||||
### SplitRight
|
||||
|
||||
Same as the Split component, but renders the first child on the right.
|
||||
|
||||
```mdx
|
||||
import { SplitRight } from 'mdx-deck/layouts'
|
||||
|
||||
<SplitRight>
|
||||
|
||||
![](kitten.png)
|
||||
|
||||
## Meow
|
||||
|
||||
</SplitRight>
|
||||
```
|
||||
|
||||
### Horizontal
|
||||
|
||||
Similar to the Split components, but renders all children side-by-side
|
||||
|
||||
### FullScreenCode
|
||||
|
||||
Render fenced code blocks fullscreen.
|
||||
|
||||
````mdx
|
||||
import { FullScreenCode } from 'mdx-deck/layouts'
|
||||
|
||||
<FullScreenCode>
|
||||
|
||||
```jsx
|
||||
<Button>Beep</Button>
|
||||
```
|
||||
|
||||
</FullScreenCode>
|
||||
````
|
||||
|
||||
## Embed
|
||||
|
||||
**Experimental**
|
||||
@ -150,7 +91,7 @@ This can be used to embed slide previews in other places, like a blog post write
|
||||
|
||||
```jsx
|
||||
import React from 'react'
|
||||
import { Embed } from '@mdx-deck/components'
|
||||
import { Embed } from 'mdx-deck'
|
||||
import deck from './my-deck.mdx'
|
||||
|
||||
export default props => (
|
||||
|
@ -1,14 +1,11 @@
|
||||
import { Head, Image, Notes, Appear } from '@mdx-deck/components'
|
||||
import { Invert, Split, SplitRight, FullScreenCode, Horizontal} from '@mdx-deck/layouts'
|
||||
import {
|
||||
Head, Image, Notes, Appear,
|
||||
Invert, Split, SplitRight, FullScreenCode, Horizontal
|
||||
} from 'mdx-deck'
|
||||
import Counter from './Counter'
|
||||
import { future } from '@mdx-deck/themes'
|
||||
|
||||
import future from '@mdx-deck/themes/future'
|
||||
import aspect from '@mdx-deck/themes/aspect'
|
||||
|
||||
export const themes = [
|
||||
future,
|
||||
aspect,
|
||||
]
|
||||
export const themes = [ future ]
|
||||
|
||||
<Head>
|
||||
<title>mdx-deck</title>
|
||||
@ -33,15 +30,10 @@ MDX-based presentation decks
|
||||
[MDX]: https://github.com/mdx-js/mdx
|
||||
|
||||
---
|
||||
import { Box } from '@rebass/emotion'
|
||||
|
||||
<Box
|
||||
fontSize={[ 6, 7 ]}
|
||||
p={4}
|
||||
color='navy'
|
||||
bg='magenta'>
|
||||
Import React components
|
||||
</Box>
|
||||
### Import and Use React Components
|
||||
|
||||
<Counter />
|
||||
|
||||
---
|
||||
|
||||
@ -112,7 +104,7 @@ class extends React.Component {
|
||||
---
|
||||
|
||||
<Image
|
||||
src='https://source.unsplash.com/random/768x2048'
|
||||
src='https://source.unsplash.com/random/768x2048?new_york'
|
||||
size='contain'
|
||||
/>
|
||||
|
||||
@ -122,19 +114,13 @@ Testing object fit
|
||||
|
||||
---
|
||||
|
||||
### Real React Components
|
||||
|
||||
<Counter />
|
||||
|
||||
---
|
||||
|
||||
<Image src='https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20' />
|
||||
<Image src='https://images.unsplash.com/photo-1462331940025-496dfbfc7564?new_york&w=2048&q=20' />
|
||||
|
||||
---
|
||||
|
||||
<Split>
|
||||
|
||||
![](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20)
|
||||
![](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?new_york&w=2048&q=20)
|
||||
|
||||
## Split Layout
|
||||
|
||||
@ -144,7 +130,7 @@ Testing object fit
|
||||
|
||||
<SplitRight>
|
||||
|
||||
![](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&q=20)
|
||||
![](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?new_york&w=2048&q=20)
|
||||
|
||||
## Split Layout
|
||||
|
||||
@ -186,7 +172,7 @@ Testing object fit
|
||||
|
||||
---
|
||||
|
||||
![](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?w=2048&h=1024&q=20&fit=crop)
|
||||
![](https://images.unsplash.com/photo-1462331940025-496dfbfc7564?new_york&w=2048&h=1024&q=20&fit=crop)
|
||||
|
||||
Inline image
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Exporting
|
||||
|
||||
## Static Bundle
|
||||
## Static Build
|
||||
|
||||
To export your deck as a static HTML page with JS bundle,
|
||||
add a `build` script to your `package.json` file.
|
||||
@ -11,29 +11,28 @@ add a `build` script to your `package.json` file.
|
||||
}
|
||||
```
|
||||
|
||||
### PDF & Screenshots
|
||||
## PDF
|
||||
|
||||
To export a deck as PDF or create a PNG screenshot, install the export CLI package:
|
||||
To export a deck as PDF, use the [`website-pdf`](https://www.npmjs.com/package/website-pdf) CLI.
|
||||
Start the MDX Deck dev server,
|
||||
then run the following command to create a PDF:
|
||||
|
||||
```sh
|
||||
npm i @mdx-deck/export
|
||||
npx website-pdf http://localhost:8000/print -o deck.pdf
|
||||
```
|
||||
|
||||
Then run the following command to create a PDF:
|
||||
## PNG
|
||||
|
||||
To export a PNG image, use the [`capture-website-cli`](https://github.com/sindresorhus/capture-website-cli) CLI.
|
||||
Start the dev server, then run the following:
|
||||
|
||||
```sh
|
||||
mdx-deck-export pdf deck.mdx
|
||||
npx capture-website-cli http://localhost:8000 deck.png
|
||||
```
|
||||
|
||||
Or export the first slide as a PNG:
|
||||
## Open Graph Image
|
||||
|
||||
```sh
|
||||
mdx-deck-export png deck.mdx
|
||||
```
|
||||
|
||||
### OG Image
|
||||
|
||||
To use the image as an open graph image, use the [Head](components.md#Head) component to add a meta tag.
|
||||
To add an open graph image, use the [Head](components.md#Head) component to add a meta tag.
|
||||
Note that the meta tag should point to a full URL, including schema and domain name.
|
||||
|
||||
```mdx
|
||||
|
51
docs/gatsby.md
Normal file
51
docs/gatsby.md
Normal file
@ -0,0 +1,51 @@
|
||||
|
||||
# Usage with Gatsby
|
||||
|
||||
The core MDX Deck application is built as a Gatsby theme.
|
||||
This means you can install MDX Deck as a theme within an existing Gatsby site to include presentations along with other content, such as a landing page or blog.
|
||||
The theme also supports adding multiple presentations to a single site.
|
||||
|
||||
Install the theme in your Gatsby site.
|
||||
|
||||
```sh
|
||||
npm i gatsby-theme-mdx-deck
|
||||
```
|
||||
|
||||
Add the theme to the `plugins` array in your configuration.
|
||||
|
||||
```js
|
||||
// gatsby-config.js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
'gatsby-theme-mdx-deck',
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Create a directory to store your presentations.
|
||||
|
||||
```sh
|
||||
mkdir decks
|
||||
```
|
||||
|
||||
Add MDX Deck presentations to this directory.
|
||||
Each deck will create a new page using the filename as its route.
|
||||
|
||||
```mdx
|
||||
<!-- decks/hello.mdx -->
|
||||
|
||||
# Hello
|
||||
|
||||
---
|
||||
|
||||
This is my presentation
|
||||
```
|
||||
|
||||
After running `gatsby develop`, this presentation should be viewable at `http://localhost:8000/hello` .
|
||||
|
||||
## Component Shadowing
|
||||
|
||||
Because MDX Deck is built as a Gatsby theme, you can leverage the component shadowing API to override any part of the interface
|
||||
and create child themes based on MDX Deck that provide custom behavior.
|
||||
|
||||
See the [gatsby-theme-mdx-deck](../packages/gatsby-theme) docs for more documentation and options.
|
@ -1,7 +1,7 @@
|
||||
# Layouts
|
||||
|
||||
Each slide can include a custom layout around its content.
|
||||
This can be used as a substitute for slide templates found in other presentation apps and libraries.
|
||||
This is a way to provide *templates* for certain slides.
|
||||
|
||||
```js
|
||||
// example Layout.js
|
||||
@ -41,6 +41,75 @@ which means you can use a nested ThemeProvider or target elements with CSS-in-JS
|
||||
|
||||
## Built-in Layouts
|
||||
|
||||
mdx-deck includes a few built-in layouts for common slide variations.
|
||||
MDX Deck includes a few built-in layouts for common slide variations.
|
||||
|
||||
### Invert
|
||||
|
||||
Inverts the foreground and background colors from the theme.
|
||||
|
||||
```mdx
|
||||
import { Invert } from 'mdx-deck/layouts'
|
||||
|
||||
# Normal
|
||||
|
||||
---
|
||||
|
||||
<Invert>
|
||||
|
||||
# Inverted
|
||||
|
||||
</Invert>
|
||||
```
|
||||
|
||||
### Split
|
||||
|
||||
Creates a horizontal layout with the first child on the left and all other children on the right.
|
||||
|
||||
```mdx
|
||||
import { Split } from 'mdx-deck/layouts'
|
||||
|
||||
<Split>
|
||||
|
||||
![](kitten.png)
|
||||
|
||||
## Meow
|
||||
|
||||
</Split>
|
||||
```
|
||||
|
||||
### SplitRight
|
||||
|
||||
Same as the Split component, but renders the first child on the right.
|
||||
|
||||
```mdx
|
||||
import { SplitRight } from 'mdx-deck/layouts'
|
||||
|
||||
<SplitRight>
|
||||
|
||||
![](kitten.png)
|
||||
|
||||
## Meow
|
||||
|
||||
</SplitRight>
|
||||
```
|
||||
|
||||
### Horizontal
|
||||
|
||||
Similar to the Split components, but renders all children side-by-side
|
||||
|
||||
### FullScreenCode
|
||||
|
||||
Renders code blocks fullscreen.
|
||||
|
||||
````mdx
|
||||
import { FullScreenCode } from 'mdx-deck/layouts'
|
||||
|
||||
<FullScreenCode>
|
||||
|
||||
```jsx
|
||||
<Button>Beep</Button>
|
||||
```
|
||||
|
||||
</FullScreenCode>
|
||||
````
|
||||
|
||||
See the [Components docs](components.md#layouts) for more.
|
||||
|
@ -7,20 +7,13 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "mdx-deck demo.mdx",
|
||||
"build": "mdx-deck build demo.mdx",
|
||||
"now-build": "yarn build"
|
||||
"build": "mdx-deck build demo.mdx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.7",
|
||||
"@emotion/styled": "^10.0.7",
|
||||
"@mdx-deck/components": "^2.5.0",
|
||||
"@mdx-deck/layouts": "^2.5.0",
|
||||
"@mdx-deck/themes": "^2.5.0",
|
||||
"@rebass/emotion": "^3.0.0",
|
||||
"mdx-deck": "^2.5.1",
|
||||
"rebass": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack-bundle-analyzer": "^3.3.2"
|
||||
"mdx-deck": "^2.5.0",
|
||||
"styled-system": "^5.0.15"
|
||||
}
|
||||
}
|
||||
|
23
docs/presenting.md
Normal file
23
docs/presenting.md
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
# Presenting
|
||||
|
||||
1. Enter presenter mode by pressing `Opt + P`
|
||||
2. Click the link at the bottom to open the presentation in another tab
|
||||
3. Move the new tab to its own window in the screen or projector that the audience sees
|
||||
4. Control the presentation from the original window
|
||||
5. Be sure to hide your mouse cursor so that it's not visible to the audience
|
||||
|
||||
## Speaker Notes
|
||||
|
||||
Notes that only show in presenter mode can be added to any slide
|
||||
by using the `<Notes />` component.
|
||||
|
||||
```mdx
|
||||
import { Notes } from 'mdx-deck'
|
||||
|
||||
# Slide Content
|
||||
|
||||
<Notes>
|
||||
Only visible in presenter mode
|
||||
</Notes>
|
||||
```
|
117
docs/themes.md
117
docs/themes.md
@ -1,128 +1,105 @@
|
||||
# Themes
|
||||
|
||||
![](images/default.png)
|
||||
Default
|
||||
```sh
|
||||
npm i @mdx-deck/themes
|
||||
```
|
||||
|
||||
![Default theme](images/default.png)
|
||||
|
||||
---
|
||||
|
||||
![Big theme](images/big.png)
|
||||
|
||||
```js
|
||||
export { default as theme } from '@mdx-deck/themes'
|
||||
import { big } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
![](images/big.png)
|
||||
Big
|
||||
![Book theme](images/book.png)
|
||||
|
||||
```js
|
||||
export { big as theme } from '@mdx-deck/themes'
|
||||
import { book } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
![](images/book.png)
|
||||
Book
|
||||
![Code theme](images/code.png)
|
||||
|
||||
```js
|
||||
export { book as theme } from '@mdx-deck/themes'
|
||||
import { code } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
![](images/code.png)
|
||||
Code
|
||||
![Comic theme](images/comic.png)
|
||||
|
||||
```js
|
||||
export { code as theme } from '@mdx-deck/themes'
|
||||
import { comic } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
![](images/comic.png)
|
||||
Comic
|
||||
![Condensed theme](images/condensed.png)
|
||||
|
||||
```js
|
||||
export { comic as theme } from '@mdx-deck/themes'
|
||||
import { condensed } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
![](images/condensed.png)
|
||||
Condensed
|
||||
![Dark theme](images/dark.png)
|
||||
|
||||
```js
|
||||
export { condensed as theme } from '@mdx-deck/themes'
|
||||
import { dark } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
![](images/dark.png)
|
||||
Dark
|
||||
![Future theme](images/future.png)
|
||||
|
||||
```js
|
||||
export { dark as theme } from '@mdx-deck/themes'
|
||||
import { future } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
![](images/future.png)
|
||||
Future
|
||||
![Hack theme](images/hack.png)
|
||||
|
||||
```js
|
||||
export { future as theme } from '@mdx-deck/themes'
|
||||
import { hack } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
![](images/hack.png)
|
||||
Hack
|
||||
![Notes theme](images/notes.png)
|
||||
|
||||
```js
|
||||
export { hack as theme } from '@mdx-deck/themes'
|
||||
import { notes } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
![](images/lobster.png)
|
||||
Lobster
|
||||
-->
|
||||
|
||||
![](images/notes.png)
|
||||
Notes
|
||||
![Script theme](images/script.png)
|
||||
|
||||
```js
|
||||
export { notes as theme } from '@mdx-deck/themes'
|
||||
import { script } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
![](images/rye.png)
|
||||
Rye
|
||||
-->
|
||||
|
||||
![](images/script.png)
|
||||
Script
|
||||
![Swiss theme](images/swiss.png)
|
||||
|
||||
```js
|
||||
export { script as theme } from '@mdx-deck/themes'
|
||||
import { swiss } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
![](images/swiss.png)
|
||||
Swiss
|
||||
![Yellow theme](images/yellow.png)
|
||||
|
||||
```js
|
||||
export { swiss as theme } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
![](images/yellow.png)
|
||||
Yellow
|
||||
|
||||
```js
|
||||
export { yellow as theme } from '@mdx-deck/themes'
|
||||
import { yellow } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
@ -130,37 +107,15 @@ export { yellow as theme } from '@mdx-deck/themes'
|
||||
Poppins
|
||||
|
||||
```js
|
||||
export { poppins as theme } from '@mdx-deck/themes'
|
||||
import { poppins } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Syntax Highlighter
|
||||
Syntax Highlighting
|
||||
|
||||
```js
|
||||
export { syntaxHighlighter as theme } from '@mdx-deck/themes'
|
||||
import { highlight } from '@mdx-deck/themes'
|
||||
import { prism } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Syntax Highlighter Prism
|
||||
|
||||
```js
|
||||
export { syntaxHighlighterPrism as theme } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Aspect 16:9
|
||||
|
||||
```js
|
||||
export { aspect as theme } from '@mdx-deck/themes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Aspect 4:3
|
||||
|
||||
```js
|
||||
export { aspect43 as theme } from '@mdx-deck/themes'
|
||||
```
|
||||
|
121
docs/theming.md
121
docs/theming.md
@ -1,52 +1,57 @@
|
||||
# Theming
|
||||
|
||||
mdx-deck uses [emotion][] for styling, making practically any part of the presentation themeable.
|
||||
MDX Deck uses [Theme UI][] and [Emotion][] for styling, making practically any part of the presentation themeable.
|
||||
|
||||
## Built-in Themes
|
||||
|
||||
mdx-deck includes several built-in themes to change the look and feel of the presentation.
|
||||
MDX Deck includes several built-in themes to change the look and feel of the presentation.
|
||||
Export `theme` from your MDX file to enable a theme.
|
||||
|
||||
```mdx
|
||||
export { dark as theme } from 'mdx-deck/themes'
|
||||
import { themes } from 'mdx-deck'
|
||||
|
||||
export const theme = themes.dark
|
||||
|
||||
# Dark Theme
|
||||
```
|
||||
|
||||
View the [List of Themes](themes.md).
|
||||
View the [Themes](themes.md) docs to see all available themes.
|
||||
|
||||
## Custom Themes
|
||||
|
||||
A custom theme can be provided by exporting `theme` from the MDX file.
|
||||
|
||||
```mdx
|
||||
export { default as theme } from './theme'
|
||||
import myTheme from './theme'
|
||||
|
||||
export const theme = myTheme
|
||||
|
||||
# Hello
|
||||
```
|
||||
|
||||
The theme should be an object with fields for fonts, colors, and CSS for individual components.
|
||||
Themes are based on [Theme UI][] and support customizing typography, color, layout, and other element styles.
|
||||
|
||||
```js
|
||||
// Example theme.js
|
||||
export default {
|
||||
// add a custom font
|
||||
font: 'Roboto, sans-serif',
|
||||
// custom colors
|
||||
fonts: {
|
||||
body: 'Roboto, sans-serif',
|
||||
monospace: '"Roboto Mono", monospace',
|
||||
},
|
||||
colors: {
|
||||
text: '#f0f',
|
||||
text: 'white',
|
||||
background: 'black',
|
||||
primary: 'blue',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Composing Themes
|
||||
|
||||
Multiple themes can be used together.
|
||||
For example, this allows the use of a syntax highlighting theme,
|
||||
along with a color theme, and a separate typography theme.
|
||||
Multiple themes can be composed together,
|
||||
allowing you to create separate themes for typography, color, and components, and mix and match them as needed.
|
||||
|
||||
To compose themes together export a `themes` array instead of a single theme.
|
||||
To compose multiple themes together, export a `themes` array instead of a single theme.
|
||||
|
||||
```mdx
|
||||
import { syntaxHighlighter } from 'mdx-deck/themes'
|
||||
@ -57,81 +62,71 @@ export const themes = [syntaxHighlighter, customTheme]
|
||||
# Cool. :sunglasses:
|
||||
```
|
||||
|
||||
Please note that themes are deep merged together and the last theme specified will override fields from themes before it.
|
||||
Note that themes are deeply merged together and the last theme specified will override fields from themes before it.
|
||||
|
||||
### Google Fonts
|
||||
## Google Fonts
|
||||
|
||||
Themes can specify a `googleFont` field to automatically add a `<link>` tag to the document head.
|
||||
Alternatively, use the `<Head />` component to add a custom `<link>` tag.
|
||||
|
||||
### Syntax Highlighting
|
||||
## Syntax Highlighting
|
||||
|
||||
By default fenced code blocks do not include any syntax highlighting.
|
||||
Themes can provide a set of custom MDX components, including a replacement for the default `code` component that can add syntax highlighting with libraries like [react-syntax-highlighter][].
|
||||
|
||||
MDX Deck includes two themes for adding syntax highlighting with [react-syntax-highlighter][]: `syntaxHighlighter` and `syntaxHighlighterPrism`.
|
||||
MDX Deck includes two themes for adding syntax highlighting with [react-syntax-highlighter][]: `highlight` and `prism`.
|
||||
|
||||
```mdx
|
||||
import { prism } from 'mdx-deck/themes'
|
||||
|
||||
export const themes = [ prism ]
|
||||
```
|
||||
|
||||
Since MDX supports using React components inline, you can also import a syntax highlighting component directly, if you prefer.
|
||||
|
||||
### Styling Elements
|
||||
```mdx
|
||||
import Highlighter from 'react-syntax-highlighter'
|
||||
|
||||
Each element can be styled with a theme.
|
||||
Add a style object (or string) to the theme to target specific elements.
|
||||
<Highlighter language='javascript'>
|
||||
{`export const hello = 'hi'`}
|
||||
</Highlighter>
|
||||
```
|
||||
|
||||
## Styling Elements
|
||||
|
||||
Add a `theme.styles` object to style specific markdown elements.
|
||||
|
||||
```js
|
||||
// example theme
|
||||
export default {
|
||||
h1: {
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em',
|
||||
},
|
||||
blockquote: {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
styles: {
|
||||
h1: {
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em',
|
||||
},
|
||||
blockquote: {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See the [reference](#reference) below for a full list of element keys.
|
||||
|
||||
## Reference
|
||||
|
||||
The following keys are available for theming:
|
||||
|
||||
- `font`: base font family
|
||||
- `monospace`: font family for `<pre>` and `<code>`
|
||||
- `colors`: object of colors used for MDX components
|
||||
- `text`: root foreground color
|
||||
- `background`: root background color
|
||||
- `code`: text color for `<pre>` and `<code>`
|
||||
- `codeBackground`: background color for `<pre>` and `<code>`
|
||||
- `css`: root CSS object
|
||||
- `heading`: CSS for all headings
|
||||
- `h1`: CSS for `<h1>`
|
||||
- `h2`: CSS for `<h2>`
|
||||
- `h3`: CSS for `<h3>`
|
||||
- `h4`: CSS for `<h4>`
|
||||
- `h5`: CSS for `<h5>`
|
||||
- `h6`: CSS for `<h6>`
|
||||
- `p`: CSS for `<p>`
|
||||
- `a`: CSS for `<a>`
|
||||
- `ul`: CSS for `<ul>`
|
||||
- `ol`: CSS for `<ol>`
|
||||
- `li`: CSS for `<li>`
|
||||
- `img`: CSS for `<img>`
|
||||
- `blockquote`: CSS for `<blockquote>`
|
||||
- `table`: CSS for `<table>`
|
||||
- `pre`: CSS for `<pre>`
|
||||
- `code`: CSS for `<code>`
|
||||
- `Slide`: CSS to apply to the wrapping Slide component
|
||||
- `components`: object of MDX components to render markdown
|
||||
- `Provider`: component for wrapping the entire app
|
||||
- `Presenter`: component for wrapping the presenter mode
|
||||
- `googleFont`: CSS HREF for adding a Google Font `<link>` tag
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
For more advanced customizations see the [Advanced Usage](advanced.md) docs.
|
||||
- `primary`: primary color
|
||||
- `fonts.body`: base font family
|
||||
- `fonts.heading`: heading font family
|
||||
- `fonts.monospace`: font family for `<pre>` and `<code>`
|
||||
- `text.heading`: styles for all headings
|
||||
- `styles.Slide`: styles for the wrapping Slide component
|
||||
- `components`: object of MDX components
|
||||
- `Provider`: component for wrapping the entire presentation
|
||||
- `googleFont`: Stylesheet URL for adding a Google Font
|
||||
|
||||
[emotion]: https://emotion.sh
|
||||
[theme ui]: https://theme-ui.com
|
||||
[mdx]: https://github.com/mdx-js/mdx
|
||||
[react-syntax-highlighter]: https://github.com/conorhastings/react-syntax-highlighter
|
||||
|
@ -1,3 +1,3 @@
|
||||
[build]
|
||||
command = "npm i yarn@latest && yarn && yarn build"
|
||||
publish = "docs/dist"
|
||||
publish = "docs/public"
|
||||
|
20
package.json
20
package.json
@ -8,22 +8,21 @@
|
||||
],
|
||||
"scripts": {
|
||||
"start": "yarn workspace @mdx-deck/docs start",
|
||||
"analyze-bundle": "yarn workspace @mdx-deck/docs start --webpack bundle-analyzer.config.js",
|
||||
"build": "yarn workspace @mdx-deck/docs build",
|
||||
"start-theme": "yarn workspace @mdx-deck/gatsby-theme start",
|
||||
"build-theme": "yarn workspace @mdx-deck/gatsby-theme build",
|
||||
"export": "yarn workspace @mdx-deck/export pdf",
|
||||
"test": "jest"
|
||||
"export": "./packages/export/cli.js http://localhost:8000/print -o docs/public/deck.pdf",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run",
|
||||
"test:dev": "start-server-and-test start http://localhost:8000 cypress:open",
|
||||
"test": "start-server-and-test start http://localhost:8000 cypress:run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.3.4",
|
||||
"@babel/preset-env": "^7.3.4",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"cypress": "^3.4.0",
|
||||
"husky": "^3.0.0",
|
||||
"jest": "^24.3.1",
|
||||
"lerna": "^3.13.1",
|
||||
"lint-staged": "^9.1.0",
|
||||
"prettier": "^1.16.4"
|
||||
"prettier": "^1.16.4",
|
||||
"start-server-and-test": "^1.9.1"
|
||||
},
|
||||
"jest": {
|
||||
"coverageReporters": [
|
||||
@ -46,8 +45,5 @@
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdx-js/mdx": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
# @mdx-deck/components
|
||||
|
||||
React components use in MDX Deck
|
||||
|
||||
https://github.com/jxnblk/mdx-deck
|
@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "@mdx-deck/components",
|
||||
"version": "2.5.0",
|
||||
"main": "src/index.js",
|
||||
"author": "Brent Jackson <jxnblk@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.7",
|
||||
"@emotion/styled": "^10.0.7",
|
||||
"@mdx-js/react": "^1.0.1",
|
||||
"@reach/router": "^1.2.1",
|
||||
"emotion-theming": "^10.0.7",
|
||||
"hhmmss": "^1.0.0",
|
||||
"lodash.merge": "^4.6.1",
|
||||
"react-swipeable": "^5.0.1",
|
||||
"resize-observer-polyfill": "^1.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mdx-deck/themes": "^2.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react": "^16.8.3",
|
||||
"react-dom": "^16.8.3",
|
||||
"react-test-renderer": "^16.8.4",
|
||||
"react-testing-library": "^6.1.2"
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import React from 'react'
|
||||
import useSteps from './useSteps'
|
||||
|
||||
export const Appear = props => {
|
||||
const arr = React.Children.toArray(props.children)
|
||||
const step = useSteps(arr.length)
|
||||
const children = arr.map((child, i) =>
|
||||
i < step
|
||||
? child
|
||||
: React.cloneElement(child, {
|
||||
style: {
|
||||
...child.props.style,
|
||||
visibility: 'hidden',
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
export default Appear
|
@ -1,51 +0,0 @@
|
||||
import React from 'react'
|
||||
import styled from '@emotion/styled'
|
||||
import FluidFontSize from './FluidFontSize'
|
||||
import useTheme from './useTheme'
|
||||
|
||||
const getPadding = ratio =>
|
||||
ratio > 1 ? (1 / ratio) * 100 + '%' : ratio * 100 + '%'
|
||||
|
||||
const paddingBottom = props => ({
|
||||
paddingBottom: getPadding(props.theme.aspectRatio),
|
||||
})
|
||||
|
||||
const Outer = styled('div')(
|
||||
{
|
||||
width: '100%',
|
||||
height: 0,
|
||||
margin: 'auto',
|
||||
position: 'relative',
|
||||
},
|
||||
paddingBottom
|
||||
)
|
||||
|
||||
const Inner = styled.div(
|
||||
{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
props => props.theme.Slide
|
||||
)
|
||||
|
||||
export default props => {
|
||||
const theme = useTheme()
|
||||
if (!theme.aspectRatio) {
|
||||
return <>{props.children}</>
|
||||
}
|
||||
|
||||
return (
|
||||
<Outer>
|
||||
<FluidFontSize base={10}>
|
||||
<Inner>{props.children}</Inner>
|
||||
</FluidFontSize>
|
||||
</Outer>
|
||||
)
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export class Catch extends React.Component {
|
||||
state = {
|
||||
err: null,
|
||||
}
|
||||
|
||||
componentDidCatch(err) {
|
||||
this.setState({ err })
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (!this.state.err) return
|
||||
this.setState({ err: null })
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.err) {
|
||||
return <pre children={this.state.err.toString()} />
|
||||
}
|
||||
return <>{this.props.children}</>
|
||||
}
|
||||
}
|
||||
|
||||
export default Catch
|
@ -1,57 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import hhmmss from 'hhmmss'
|
||||
import Pre from './Pre'
|
||||
|
||||
const spacer = <div style={{ margin: 4 }} />
|
||||
|
||||
let timer
|
||||
|
||||
const Clock = props => {
|
||||
const [time, setTime] = useState(new Date().toLocaleTimeString())
|
||||
const [seconds, setSeconds] = useState(0)
|
||||
const [on, setTimer] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const tick = () => {
|
||||
const now = new Date()
|
||||
setTime(now.toLocaleTimeString())
|
||||
if (on) setSeconds(seconds + 1)
|
||||
}
|
||||
timer = setInterval(tick, 1000)
|
||||
return () => {
|
||||
clearInterval(timer)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
disabled={!seconds || on}
|
||||
onClick={e => {
|
||||
setSeconds(0)
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
{spacer}
|
||||
<button
|
||||
onClick={e => {
|
||||
setTimer(!on)
|
||||
}}
|
||||
>
|
||||
{on ? 'Pause' : 'Start'}
|
||||
</button>
|
||||
{spacer}
|
||||
<Pre>
|
||||
{hhmmss(seconds)} | {time}
|
||||
</Pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Clock
|
@ -1,85 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
// experimental component for embedding MDX Decks
|
||||
// in other React applications
|
||||
/*
|
||||
*
|
||||
* import React from 'react'
|
||||
* import { DeckEmbed } from '@mdx-deck/components'
|
||||
* import deck from './my-deck.mdx'
|
||||
*
|
||||
* export default props =>
|
||||
* <>
|
||||
* <h1>The first slide</h1>
|
||||
* <DeckEmbed src={deck} />
|
||||
* <h1>The second slide</h1>
|
||||
* <DeckEmbed src={deck} slide={2} />
|
||||
* </>
|
||||
*
|
||||
*/
|
||||
|
||||
import { jsx } from '@emotion/core'
|
||||
import Provider from './Provider'
|
||||
import Slide from './Slide'
|
||||
import GoogleFonts from './GoogleFonts'
|
||||
import splitSlides from './splitSlides'
|
||||
|
||||
// fix for regression in gatsby-theme
|
||||
import merge from 'lodash.merge'
|
||||
import defaultTheme from '@mdx-deck/themes/base'
|
||||
|
||||
const Placeholder = ({ index }) => (
|
||||
<pre style={{ fontSize: 16 }}>not found: slide {index}</pre>
|
||||
)
|
||||
|
||||
// fix for regression in gatsby-theme
|
||||
const mergeThemes = themes =>
|
||||
themes.reduce(
|
||||
(acc, theme) =>
|
||||
typeof theme === 'function' ? theme(acc) : merge(acc, theme),
|
||||
{}
|
||||
)
|
||||
|
||||
const wrapper = props => {
|
||||
const { slides, theme: baseTheme, themes, ratio, zoom } = splitSlides(props)
|
||||
// fix for regression in gatsby-theme
|
||||
const theme = mergeThemes([
|
||||
defaultTheme,
|
||||
baseTheme,
|
||||
...themes,
|
||||
{
|
||||
aspectRatio: ratio,
|
||||
Slide: {
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
},
|
||||
},
|
||||
])
|
||||
const Content = slides[props.slide - 1] || Placeholder
|
||||
|
||||
return (
|
||||
<Provider theme={theme}>
|
||||
<GoogleFonts />
|
||||
<Slide zoom={zoom}>
|
||||
<Content index={props.slide - 1} />
|
||||
</Slide>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const Embed = ({
|
||||
src: Deck,
|
||||
slide = 1,
|
||||
ratio = 16 / 9,
|
||||
zoom = 1,
|
||||
...props
|
||||
}) => (
|
||||
<Deck
|
||||
{...props}
|
||||
components={{ wrapper }}
|
||||
slide={slide}
|
||||
ratio={ratio}
|
||||
zoom={zoom}
|
||||
/>
|
||||
)
|
||||
|
||||
export default Embed
|
@ -1,31 +0,0 @@
|
||||
// prototype for fluid resizable font size
|
||||
import React, { useLayoutEffect, useRef, useState } from 'react'
|
||||
import ResizeObserver from 'resize-observer-polyfill'
|
||||
|
||||
export const FluidFontSize = ({ base = 16, children, className }) => {
|
||||
const div = useRef(null)
|
||||
const [fontSize, setFontSize] = useState(base)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const observer = new ResizeObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.target !== div.current) return
|
||||
const { width } = entry.contentRect
|
||||
const ratio = width / 320
|
||||
const next = Math.floor(ratio * base)
|
||||
setFontSize(next)
|
||||
})
|
||||
})
|
||||
observer.observe(div.current)
|
||||
|
||||
return () => observer.unobserve(div.current)
|
||||
}, [base])
|
||||
|
||||
return (
|
||||
<div ref={div} className={className} style={{ fontSize }}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FluidFontSize
|
@ -1,14 +0,0 @@
|
||||
import React from 'react'
|
||||
import { withTheme } from 'emotion-theming'
|
||||
import { Head } from './Head'
|
||||
|
||||
const GoogleFonts = withTheme(({ theme }) => {
|
||||
if (!theme.googleFont) return false
|
||||
return (
|
||||
<Head portal>
|
||||
<link rel="stylesheet" href={theme.googleFont} />
|
||||
</Head>
|
||||
)
|
||||
})
|
||||
|
||||
export default GoogleFonts
|
@ -1,66 +0,0 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import Zoom from './Zoom'
|
||||
import Slide from './Slide'
|
||||
|
||||
export const Grid = props => {
|
||||
const { index, slides, modes, update, goto } = props
|
||||
const activeThumb = React.createRef()
|
||||
|
||||
useEffect(() => {
|
||||
const el = activeThumb.current
|
||||
if (!el) return
|
||||
if (typeof el.scrollIntoViewIfNeeded === 'function') {
|
||||
el.scrollIntoViewIfNeeded()
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100vh',
|
||||
overflowY: 'auto',
|
||||
color: 'white',
|
||||
backgroundColor: 'black',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
{slides.map((Component, i) => (
|
||||
<div
|
||||
ref={i === index ? activeThumb : null}
|
||||
key={i}
|
||||
role="link"
|
||||
onClick={e => {
|
||||
goto(i)
|
||||
update({ mode: modes.NORMAL })
|
||||
}}
|
||||
style={{
|
||||
display: 'block',
|
||||
width: 'calc(25vw - 4px)',
|
||||
height: 'calc(25vh - 4px)',
|
||||
margin: '2px',
|
||||
overflow: 'hidden',
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
cursor: 'pointer',
|
||||
outline: i === index ? '4px solid #0cf' : null,
|
||||
}}
|
||||
>
|
||||
<Zoom zoom={1 / 4}>
|
||||
<Slide>
|
||||
<Component />
|
||||
</Slide>
|
||||
</Zoom>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Grid
|
@ -1,60 +0,0 @@
|
||||
import React, { useEffect, useContext } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
let didMount = false
|
||||
|
||||
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 <HeadContext.Provider value={context}>{children}</HeadContext.Provider>
|
||||
}
|
||||
|
||||
// get head for all slides
|
||||
export const UserHead = ({ mdx }) =>
|
||||
!!mdx &&
|
||||
React.createElement(mdx, {
|
||||
components: {
|
||||
wrapper: props => {
|
||||
if (!didMount) return false
|
||||
const heads = React.Children.toArray(props.children).filter(
|
||||
child => child.props.originalType === Head
|
||||
)
|
||||
const head = React.Children.toArray(
|
||||
heads.reduce(
|
||||
(acc, head) => [
|
||||
...acc,
|
||||
...React.Children.toArray(head.props.children),
|
||||
],
|
||||
[]
|
||||
)
|
||||
)
|
||||
return createPortal(head, document.head)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const Head = props => {
|
||||
const { push } = useContext(HeadContext)
|
||||
const children = React.Children.toArray(props.children)
|
||||
|
||||
useEffect(() => {
|
||||
didMount = true
|
||||
}, [])
|
||||
if (!didMount) {
|
||||
push(children)
|
||||
return false
|
||||
}
|
||||
if (!props.portal) return false
|
||||
return createPortal(children, document.head)
|
||||
}
|
||||
|
||||
export default Head
|
@ -1,22 +0,0 @@
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
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
|
@ -1,81 +0,0 @@
|
||||
import { useEffect } from 'react'
|
||||
import { globalHistory, navigate } from '@reach/router'
|
||||
|
||||
const keys = {
|
||||
right: 39,
|
||||
left: 37,
|
||||
space: 32,
|
||||
p: 80,
|
||||
o: 79,
|
||||
g: 71,
|
||||
pgUp: 33,
|
||||
pgDown: 34,
|
||||
}
|
||||
|
||||
const inputElements = ['INPUT', 'TEXTAREA', 'A', 'BUTTON']
|
||||
|
||||
const toggleMode = key => state => ({
|
||||
mode: state.mode === key ? 'normal' : key,
|
||||
})
|
||||
|
||||
const handleKeyDown = props => e => {
|
||||
const { basepath, update, modes } = props
|
||||
const { keyCode, metaKey, ctrlKey, altKey, shiftKey } = e
|
||||
const { activeElement } = document
|
||||
|
||||
if (inputElements.includes(activeElement.tagName)) {
|
||||
return
|
||||
}
|
||||
if (metaKey || ctrlKey) return
|
||||
const alt = altKey && !shiftKey
|
||||
|
||||
const { pathname } = globalHistory.location
|
||||
if (keyCode === keys.p && shiftKey && altKey) {
|
||||
navigate(basepath + '/print')
|
||||
update({ mode: modes.PRINT })
|
||||
}
|
||||
if (pathname === '/print') return
|
||||
|
||||
if (alt) {
|
||||
switch (keyCode) {
|
||||
case keys.p:
|
||||
update(toggleMode(modes.PRESENTER))
|
||||
break
|
||||
case keys.o:
|
||||
update(toggleMode(modes.OVERVIEW))
|
||||
break
|
||||
case keys.g:
|
||||
update(toggleMode(modes.GRID))
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else {
|
||||
switch (keyCode) {
|
||||
case keys.pgUp:
|
||||
case keys.left:
|
||||
e.preventDefault()
|
||||
props.previous()
|
||||
break
|
||||
case keys.pgDown:
|
||||
case keys.right:
|
||||
case keys.space:
|
||||
e.preventDefault()
|
||||
props.next()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default props => {
|
||||
useEffect(() => {
|
||||
const handler = handleKeyDown(props)
|
||||
window.addEventListener('keydown', handler)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handler)
|
||||
}
|
||||
}, [props.metadata])
|
||||
return false
|
||||
}
|
@ -1,203 +0,0 @@
|
||||
import React, { useContext, useReducer } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Router, globalHistory, navigate } from '@reach/router'
|
||||
import { Swipeable } from 'react-swipeable'
|
||||
import merge from 'lodash.merge'
|
||||
import defaultTheme from '@mdx-deck/themes/base'
|
||||
|
||||
import Provider from './Provider'
|
||||
import Slide from './Slide'
|
||||
import Presenter from './Presenter'
|
||||
import Overview from './Overview'
|
||||
import Grid from './Grid'
|
||||
import Print from './Print'
|
||||
import GoogleFonts from './GoogleFonts'
|
||||
import Catch from './Catch'
|
||||
import Keyboard from './Keyboard'
|
||||
import Storage from './Storage'
|
||||
import QueryString from './QueryString'
|
||||
import Style from './Style'
|
||||
|
||||
const NORMAL = 'normal'
|
||||
const PRESENTER = 'presenter'
|
||||
const OVERVIEW = 'overview'
|
||||
const GRID = 'grid'
|
||||
const PRINT = 'print'
|
||||
const modes = {
|
||||
NORMAL,
|
||||
PRESENTER,
|
||||
OVERVIEW,
|
||||
GRID,
|
||||
PRINT,
|
||||
}
|
||||
|
||||
const BaseWrapper = props => <>{props.children}</>
|
||||
|
||||
const getIndex = ({ basepath }) => {
|
||||
const { pathname } = globalHistory.location
|
||||
const root = pathname.replace(basepath, '')
|
||||
const n = Number(root.split('/')[1])
|
||||
const index = isNaN(n) ? 0 : n
|
||||
return index
|
||||
}
|
||||
|
||||
const mergeThemes = themes =>
|
||||
themes.reduce(
|
||||
(acc, theme) =>
|
||||
typeof theme === 'function' ? theme(acc) : merge(acc, theme),
|
||||
{}
|
||||
)
|
||||
|
||||
const mergeState = (state, next) =>
|
||||
merge({}, state, typeof next === 'function' ? next(state) : next)
|
||||
const useState = init => useReducer(mergeState, init)
|
||||
|
||||
export const MDXDeckContext = React.createContext()
|
||||
|
||||
const useDeckState = () => {
|
||||
const context = useContext(MDXDeckContext)
|
||||
if (context) return context
|
||||
|
||||
const [state, setState] = useState({
|
||||
metadata: {},
|
||||
step: 0,
|
||||
mode: NORMAL,
|
||||
})
|
||||
|
||||
return {
|
||||
state,
|
||||
setState,
|
||||
}
|
||||
}
|
||||
|
||||
export const MDXDeckState = ({ children }) => {
|
||||
const context = useDeckState()
|
||||
return <MDXDeckContext.Provider value={context} children={children} />
|
||||
}
|
||||
|
||||
export const MDXDeck = props => {
|
||||
const { slides, basepath, theme: baseTheme, themes = [] } = props
|
||||
const { state, setState } = useDeckState(MDXDeckContext)
|
||||
const theme = mergeThemes([defaultTheme, baseTheme, ...themes])
|
||||
|
||||
const index = getIndex(props)
|
||||
|
||||
const getMeta = i => {
|
||||
return state.metadata[i] || {}
|
||||
}
|
||||
|
||||
const getWrapper = mode => {
|
||||
switch (mode) {
|
||||
case PRESENTER:
|
||||
return theme.Presenter || Presenter
|
||||
case OVERVIEW:
|
||||
return Overview
|
||||
case GRID:
|
||||
return Grid
|
||||
default:
|
||||
return BaseWrapper
|
||||
}
|
||||
}
|
||||
|
||||
const register = (index, meta) => {
|
||||
setState({
|
||||
metadata: {
|
||||
...state.metadata,
|
||||
[index]: {
|
||||
...state.metadata[index],
|
||||
...meta,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const goto = nextIndex => {
|
||||
const current = getIndex(props)
|
||||
const reverse = nextIndex < current
|
||||
const { search } = globalHistory.location
|
||||
navigate(basepath + '/' + nextIndex + search)
|
||||
const meta = getMeta(nextIndex)
|
||||
setState({
|
||||
step: reverse ? meta.steps || 0 : 0,
|
||||
})
|
||||
}
|
||||
|
||||
const previous = () => {
|
||||
const current = getIndex(props)
|
||||
const meta = getMeta(current)
|
||||
if (meta.steps && state.step > 0) {
|
||||
setState({ step: state.step - 1 })
|
||||
} else {
|
||||
const p = current - 1
|
||||
if (p < 0) return
|
||||
goto(p)
|
||||
}
|
||||
}
|
||||
|
||||
const next = () => {
|
||||
const current = getIndex(props)
|
||||
const meta = getMeta(current)
|
||||
if (meta.steps && state.step < meta.steps) {
|
||||
setState({ step: state.step + 1 })
|
||||
} else {
|
||||
const n = current + 1
|
||||
if (n > slides.length - 1) return
|
||||
goto(n)
|
||||
}
|
||||
}
|
||||
|
||||
const context = {
|
||||
...state,
|
||||
update: setState,
|
||||
register,
|
||||
length: slides.length,
|
||||
modes,
|
||||
index,
|
||||
goto,
|
||||
previous,
|
||||
next,
|
||||
}
|
||||
|
||||
const [First] = slides
|
||||
const Wrapper = getWrapper(state.mode)
|
||||
|
||||
return (
|
||||
<Provider {...props} {...context} theme={theme}>
|
||||
<Catch>
|
||||
<Style {...context} />
|
||||
<Keyboard {...props} {...context} />
|
||||
<Storage {...context} />
|
||||
<QueryString {...context} />
|
||||
<GoogleFonts />
|
||||
<Wrapper {...props} {...context}>
|
||||
<Swipeable onSwipedRight={previous} onSwipedLeft={next}>
|
||||
<Router basepath={basepath}>
|
||||
<Slide path="/" index={0} context={context}>
|
||||
<First path="/" />
|
||||
</Slide>
|
||||
{slides.map((Component, i) => (
|
||||
<Slide key={i} path={i + '/*'} index={i} context={context}>
|
||||
<Component path={i + '/*'} />
|
||||
</Slide>
|
||||
))}
|
||||
<Print path="print" {...props} />
|
||||
</Router>
|
||||
</Swipeable>
|
||||
</Wrapper>
|
||||
</Catch>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
MDXDeck.propTypes = {
|
||||
slides: PropTypes.array.isRequired,
|
||||
headTags: PropTypes.array.isRequired,
|
||||
}
|
||||
|
||||
MDXDeck.defaultProps = {
|
||||
basepath: '',
|
||||
slides: [],
|
||||
headTags: [],
|
||||
}
|
||||
|
||||
export default MDXDeck
|
@ -1,17 +0,0 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useDeck } from './context'
|
||||
|
||||
export const Notes = props => {
|
||||
const context = useDeck()
|
||||
useEffect(() => {
|
||||
if (!context || !context.register) return
|
||||
if (typeof context.index === 'undefined') return
|
||||
context.register(context.index, {
|
||||
notes: props.children,
|
||||
})
|
||||
}, [])
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export default Notes
|
@ -1,80 +0,0 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import Zoom from './Zoom'
|
||||
import Slide from './Slide'
|
||||
import Pre from './Pre'
|
||||
|
||||
export const Overview = props => {
|
||||
const { goto, index, slides } = props
|
||||
const activeThumb = React.createRef()
|
||||
|
||||
useEffect(() => {
|
||||
const el = activeThumb.current
|
||||
if (!el) return
|
||||
if (typeof el.scrollIntoViewIfNeeded === 'function') {
|
||||
el.scrollIntoViewIfNeeded()
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
height: '100vh',
|
||||
color: 'white',
|
||||
backgroundColor: 'black',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
flex: 'none',
|
||||
height: '100vh',
|
||||
paddingLeft: 4,
|
||||
paddingRight: 4,
|
||||
overflowY: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
>
|
||||
{slides.map((Component, i) => (
|
||||
<div
|
||||
ref={i === index ? activeThumb : null}
|
||||
key={i}
|
||||
role="link"
|
||||
onClick={e => {
|
||||
goto(i)
|
||||
}}
|
||||
style={{
|
||||
display: 'block',
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
padding: 0,
|
||||
marginTop: 4,
|
||||
marginBottom: 4,
|
||||
cursor: 'pointer',
|
||||
outline: i === index ? '4px solid #0cf' : null,
|
||||
}}
|
||||
>
|
||||
<Zoom zoom={1 / 6}>
|
||||
<Slide>
|
||||
<Component />
|
||||
</Slide>
|
||||
</Zoom>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: 200 / 3 + '%',
|
||||
margin: 'auto',
|
||||
}}
|
||||
>
|
||||
<Zoom zoom={2 / 3}>{props.children}</Zoom>
|
||||
<Pre>
|
||||
{index} of {slides.length - 1}
|
||||
</Pre>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Overview
|
@ -1,13 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export default props => (
|
||||
<pre
|
||||
{...props}
|
||||
style={{
|
||||
fontFamily: 'Menlo, monospace',
|
||||
fontSize: 18,
|
||||
whiteSpace: 'pre-wrap',
|
||||
...props.style,
|
||||
}}
|
||||
/>
|
||||
)
|
@ -1,86 +0,0 @@
|
||||
import React from 'react'
|
||||
import { globalHistory } from '@reach/router'
|
||||
import Zoom from './Zoom'
|
||||
import Slide from './Slide'
|
||||
import Pre from './Pre'
|
||||
import Clock from './Clock'
|
||||
|
||||
export const Presenter = props => {
|
||||
const { slides, metadata, index } = props
|
||||
const Next = slides[index + 1]
|
||||
const { notes } = metadata[index] || {}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
color: 'white',
|
||||
backgroundColor: 'black',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 500 / 8 + '%',
|
||||
minWidth: 0,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
>
|
||||
<Zoom zoom={5 / 8}>{props.children}</Zoom>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: 100 / 4 + '%',
|
||||
minWidth: 0,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
>
|
||||
<Zoom zoom={1 / 4}>
|
||||
{Next && (
|
||||
<Slide>
|
||||
<Next />
|
||||
</Slide>
|
||||
)}
|
||||
</Zoom>
|
||||
<Pre>{notes}</Pre>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
<Pre>
|
||||
{index} of {slides.length - 1}
|
||||
</Pre>
|
||||
<div style={{ margin: 'auto' }} />
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={globalHistory.location.origin + globalHistory.location.pathname}
|
||||
style={{
|
||||
color: 'inherit',
|
||||
}}
|
||||
>
|
||||
Open New Window
|
||||
</a>
|
||||
<div style={{ margin: 'auto' }} />
|
||||
<Clock />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Presenter
|
@ -1,14 +0,0 @@
|
||||
import React from 'react'
|
||||
import Slide from './Slide'
|
||||
|
||||
export const Print = props => (
|
||||
<>
|
||||
{props.slides.map((Component, i) => (
|
||||
<Slide key={i} index={i}>
|
||||
<Component />
|
||||
</Slide>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
export default Print
|
@ -1,33 +0,0 @@
|
||||
import React from 'react'
|
||||
import { ThemeProvider } from 'emotion-theming'
|
||||
import { HeadProvider, UserHead } from './Head'
|
||||
import { MDXProvider } from '@mdx-js/react'
|
||||
import mdxComponents from './mdx-components'
|
||||
|
||||
const DefaultProvider = props => <>{props.children}</>
|
||||
|
||||
export const Provider = props => {
|
||||
const { headTags, theme, mdx } = props
|
||||
const {
|
||||
Provider: UserProvider = DefaultProvider,
|
||||
components: themeComponents = {},
|
||||
} = theme
|
||||
|
||||
const allComponents = {
|
||||
...mdxComponents,
|
||||
...themeComponents,
|
||||
}
|
||||
|
||||
return (
|
||||
<HeadProvider tags={headTags}>
|
||||
<UserHead mdx={mdx} />
|
||||
<ThemeProvider theme={theme}>
|
||||
<MDXProvider components={allComponents}>
|
||||
<UserProvider {...props} />
|
||||
</MDXProvider>
|
||||
</ThemeProvider>
|
||||
</HeadProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default Provider
|
@ -1,31 +0,0 @@
|
||||
import { useEffect } from 'react'
|
||||
import { globalHistory, navigate } from '@reach/router'
|
||||
import querystring from 'querystring'
|
||||
|
||||
const getQuery = () => {
|
||||
const query = querystring.parse(
|
||||
globalHistory.location.search.replace(/^\?/, '')
|
||||
)
|
||||
return query
|
||||
}
|
||||
|
||||
export default ({ mode, modes, update, index }) => {
|
||||
useEffect(() => {
|
||||
const state = getQuery()
|
||||
update(state)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const { pathname, search } = globalHistory.location
|
||||
if (mode !== modes.NORMAL && mode !== modes.PRINT) {
|
||||
const query = '?' + querystring.stringify({ mode })
|
||||
if (query === search) return
|
||||
navigate(query)
|
||||
} else {
|
||||
if (!search) return
|
||||
navigate(pathname)
|
||||
}
|
||||
}, [mode])
|
||||
|
||||
return false
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export const Ratio = ({ ratio, children }) => (
|
||||
<div
|
||||
css={{
|
||||
width: '100%',
|
||||
height: 0,
|
||||
paddingBottom: ratio * 100 + '%',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Ratio
|
@ -1,90 +0,0 @@
|
||||
import React from 'react'
|
||||
import styled from '@emotion/styled'
|
||||
import { Context } from './context'
|
||||
import AspectRatioSlide from './AspectRatioSlide'
|
||||
|
||||
const themed = (...tags) => props =>
|
||||
tags.map(tag => props.theme[tag] && { ['& ' + tag]: props.theme[tag] })
|
||||
|
||||
const themedHeadings = props => ({
|
||||
'& h1, & h2, & h3, & h4, & h5, & h6': props.theme.heading,
|
||||
})
|
||||
|
||||
const themedLinks = props => ({
|
||||
'& a': {
|
||||
color: props.theme.colors.link,
|
||||
},
|
||||
})
|
||||
|
||||
// backwards compatibility
|
||||
const themedQuote = props => ({
|
||||
'& blockquote': props.theme.quote,
|
||||
})
|
||||
|
||||
const themedCode = props => ({
|
||||
'& code, & pre': {
|
||||
fontFamily: props.theme.monospace,
|
||||
color: props.theme.colors.code,
|
||||
background: props.theme.colors.codeBackground,
|
||||
},
|
||||
})
|
||||
|
||||
const Root = styled.div(
|
||||
props => ({
|
||||
fontFamily: props.theme.font,
|
||||
color: props.theme.colors.text,
|
||||
backgroundColor: props.theme.colors.background,
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
props => props.theme.css,
|
||||
props => props.theme.Slide,
|
||||
themedLinks,
|
||||
themedHeadings,
|
||||
themedCode,
|
||||
themedQuote,
|
||||
themed(
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'a',
|
||||
'ul',
|
||||
'ol',
|
||||
'li',
|
||||
'p',
|
||||
'blockquote',
|
||||
'img',
|
||||
'table',
|
||||
'pre',
|
||||
'code'
|
||||
)
|
||||
)
|
||||
|
||||
export const Slide = ({ index, context, ...props }) => (
|
||||
<Context.Provider
|
||||
value={{
|
||||
index,
|
||||
...context,
|
||||
}}
|
||||
>
|
||||
<Root>
|
||||
<AspectRatioSlide {...props} />
|
||||
</Root>
|
||||
</Context.Provider>
|
||||
)
|
||||
|
||||
Slide.defaultProps = {
|
||||
context: {
|
||||
step: Infinity,
|
||||
},
|
||||
}
|
||||
|
||||
export default Slide
|
@ -1,8 +0,0 @@
|
||||
import useSteps from './useSteps'
|
||||
|
||||
export const Steps = props => {
|
||||
const step = useSteps(props.length)
|
||||
return props.render({ step })
|
||||
}
|
||||
|
||||
export default Steps
|
@ -1,56 +0,0 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const STORAGE_INDEX = 'mdx-slide'
|
||||
const STORAGE_STEP = 'mdx-step'
|
||||
|
||||
export const useLocalStorage = (handler, args = []) => {
|
||||
const [focused, setFocused] = useState(false)
|
||||
const handleFocus = () => {
|
||||
setFocused(true)
|
||||
}
|
||||
const handleBlur = () => {
|
||||
setFocused(false)
|
||||
}
|
||||
useEffect(() => {
|
||||
setFocused(document.hasFocus())
|
||||
if (!focused) window.addEventListener('storage', handler)
|
||||
window.addEventListener('focus', handleFocus)
|
||||
window.addEventListener('blur', handleBlur)
|
||||
return () => {
|
||||
if (!focused) window.removeEventListener('storage', handler)
|
||||
window.removeEventListener('focus', handleFocus)
|
||||
window.removeEventListener('blur', handleBlur)
|
||||
}
|
||||
}, [focused, ...args])
|
||||
}
|
||||
|
||||
export const useSetStorage = (key, value) => {
|
||||
useEffect(() => {
|
||||
localStorage.setItem(key, value)
|
||||
}, [key, value])
|
||||
}
|
||||
|
||||
const handleStorageChange = ({ goto, update }) => e => {
|
||||
const { key } = e
|
||||
switch (key) {
|
||||
case STORAGE_INDEX:
|
||||
const index = parseInt(e.newValue, 10)
|
||||
goto(index)
|
||||
break
|
||||
case STORAGE_STEP:
|
||||
const step = parseInt(e.newValue, 10)
|
||||
update({ step })
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
export default ({ goto, update, index, step }) => {
|
||||
const handler = handleStorageChange({ goto, update })
|
||||
useLocalStorage(handler)
|
||||
useSetStorage(STORAGE_INDEX, index)
|
||||
useSetStorage(STORAGE_STEP, step)
|
||||
|
||||
return false
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import React from 'react'
|
||||
import { globalHistory } from '@reach/router'
|
||||
import { Global } from '@emotion/core'
|
||||
|
||||
const isPrintPath = () => {
|
||||
const { pathname } = globalHistory.location
|
||||
const parts = pathname.split('/')
|
||||
const path = parts[parts.length - 1]
|
||||
return path === 'print'
|
||||
}
|
||||
|
||||
export default ({ mode, modes }) => {
|
||||
if (mode === modes.PRINT) return false
|
||||
if (isPrintPath()) return false
|
||||
return (
|
||||
<Global
|
||||
styles={{
|
||||
body: {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import React from 'react'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
const ZoomRoot = styled.div(props => ({
|
||||
backgroundColor: props.theme.colors.background,
|
||||
width: `calc(${100 * props.zoom}vw)`,
|
||||
height: `calc(${100 * props.zoom}vh)`,
|
||||
}))
|
||||
|
||||
const ZoomInner = styled.div([], props => ({
|
||||
transformOrigin: '0 0',
|
||||
transform: `scale(${props.zoom})`,
|
||||
}))
|
||||
|
||||
export const Zoom = ({ zoom, ...props }) => (
|
||||
<ZoomRoot zoom={zoom}>
|
||||
<ZoomInner zoom={zoom} {...props} />
|
||||
</ZoomRoot>
|
||||
)
|
||||
|
||||
Zoom.defaultProps = {
|
||||
zoom: 1,
|
||||
}
|
||||
|
||||
export default Zoom
|
@ -1,12 +0,0 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import Appear from '../Appear'
|
||||
|
||||
test('Appear renders', () => {
|
||||
const json = TestRenderer.create(
|
||||
<Appear>
|
||||
<h1>Hello</h1>
|
||||
</Appear>
|
||||
).toJSON()
|
||||
expect(json).toMatchSnapshot()
|
||||
})
|
@ -1,5 +0,0 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import Clock from '../Clock'
|
||||
|
||||
test.todo('Clock renders')
|
@ -1,5 +0,0 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import GoogleFonts from '../GoogleFonts'
|
||||
|
||||
test.todo('GoogleFonts renders')
|
@ -1,15 +0,0 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import { Head, HeadProvider } from '../Head'
|
||||
|
||||
test.skip('Head populates HeadProvider‘s tag prop', () => {
|
||||
const tags = []
|
||||
TestRenderer.create(
|
||||
<HeadProvider tags={tags}>
|
||||
<Head>
|
||||
<title>Hello</title>
|
||||
</Head>
|
||||
</HeadProvider>
|
||||
)
|
||||
expect(tags.length).toBe(1)
|
||||
})
|
@ -1,35 +0,0 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { render, cleanup } from 'react-testing-library'
|
||||
import renderer from 'react-test-renderer'
|
||||
import { MDXDeckState, MDXDeckContext, MDXDeck } from '../MDXDeck'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
const slides = [() => <pre>one</pre>, () => <pre>two</pre>]
|
||||
|
||||
describe('MDXDeckState', () => {
|
||||
test('provides state', () => {
|
||||
let context
|
||||
const Consumer = props => {
|
||||
context = useContext(MDXDeckContext)
|
||||
return false
|
||||
}
|
||||
const deck = renderer.create(
|
||||
<MDXDeckState>
|
||||
<Consumer />
|
||||
</MDXDeckState>
|
||||
)
|
||||
expect(typeof context.state).toBe('object')
|
||||
expect(typeof context.state.metadata).toBe('object')
|
||||
expect(context.state.step).toBe(0)
|
||||
expect(context.state.mode).toBe('normal')
|
||||
expect(typeof context.setState).toBe('function')
|
||||
})
|
||||
|
||||
test.todo('setState updates state')
|
||||
})
|
||||
|
||||
test('renders', () => {
|
||||
const json = renderer.create(<MDXDeck slides={slides} />).toJSON()
|
||||
expect(json).toMatchSnapshot()
|
||||
})
|
@ -1,8 +0,0 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import Pre from '../Pre'
|
||||
|
||||
test('Pre renders', () => {
|
||||
const json = TestRenderer.create(<Pre children="hi" />).toJSON()
|
||||
expect(json).toMatchSnapshot()
|
||||
})
|
@ -1,8 +0,0 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
import Steps from '../Steps'
|
||||
|
||||
test('Steps renders', () => {
|
||||
const json = TestRenderer.create(<Steps render={() => 'hi'} />).toJSON()
|
||||
expect(json).toMatchSnapshot()
|
||||
})
|
@ -1,13 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Appear renders 1`] = `
|
||||
<h1
|
||||
style={
|
||||
Object {
|
||||
"visibility": "hidden",
|
||||
}
|
||||
}
|
||||
>
|
||||
Hello
|
||||
</h1>
|
||||
`;
|
@ -1,33 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders 1`] = `
|
||||
<div>
|
||||
<div
|
||||
role="group"
|
||||
style={
|
||||
Object {
|
||||
"outline": "none",
|
||||
}
|
||||
}
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="css-suf238-Root ekm8v3h0"
|
||||
>
|
||||
<div
|
||||
role="group"
|
||||
style={
|
||||
Object {
|
||||
"outline": "none",
|
||||
}
|
||||
}
|
||||
tabIndex="-1"
|
||||
>
|
||||
<pre>
|
||||
one
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -1,15 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Pre renders 1`] = `
|
||||
<pre
|
||||
style={
|
||||
Object {
|
||||
"fontFamily": "Menlo, monospace",
|
||||
"fontSize": 18,
|
||||
"whiteSpace": "pre-wrap",
|
||||
}
|
||||
}
|
||||
>
|
||||
hi
|
||||
</pre>
|
||||
`;
|
@ -1,3 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Steps renders 1`] = `"hi"`;
|
@ -1,11 +0,0 @@
|
||||
// slide context
|
||||
import React, { useContext } from 'react'
|
||||
|
||||
export const Context = React.createContext({})
|
||||
|
||||
export const useDeck = () => useContext(Context)
|
||||
|
||||
export const withContext = Component => props => {
|
||||
const context = useDeck()
|
||||
return <Component {...props} context={context} />
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
export { MDXDeck, MDXDeckState } from './MDXDeck'
|
||||
export { default as Clock } from './Clock'
|
||||
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'
|
||||
export { default as useSteps } from './useSteps'
|
||||
export { default as useTheme } from './useTheme'
|
||||
|
||||
export { Slide } from './Slide'
|
||||
export { Zoom } from './Zoom'
|
||||
|
||||
export { Embed } from './Embed'
|
||||
|
||||
// private
|
||||
export { splitSlides } from './splitSlides'
|
||||
export { Ratio } from './Ratio'
|
||||
export { Provider } from './Provider'
|
@ -1,57 +0,0 @@
|
||||
import React from 'react'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
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 => (
|
||||
<TableWrap>
|
||||
<Table {...props} />
|
||||
</TableWrap>
|
||||
)
|
||||
|
||||
export const components = {
|
||||
wrapper: props => props.children,
|
||||
pre: props => props.children,
|
||||
code,
|
||||
inlineCode,
|
||||
img,
|
||||
table,
|
||||
}
|
||||
|
||||
export default components
|
@ -1,29 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export const splitSlides = props => {
|
||||
const { theme, themes } = props
|
||||
const arr = React.Children.toArray(props.children)
|
||||
const splits = []
|
||||
const slides = []
|
||||
|
||||
arr.forEach((child, i) => {
|
||||
if (child.props.mdxType === 'hr') splits.push(i)
|
||||
})
|
||||
|
||||
let previousSplit = 0
|
||||
splits.forEach(i => {
|
||||
const children = [...arr.slice(previousSplit, i)]
|
||||
slides.push(() => children)
|
||||
previousSplit = i + 1
|
||||
})
|
||||
slides.push(() => [...arr.slice(previousSplit)])
|
||||
|
||||
return {
|
||||
...props,
|
||||
theme,
|
||||
themes,
|
||||
slides,
|
||||
}
|
||||
}
|
||||
|
||||
export default splitSlides
|
@ -1,13 +0,0 @@
|
||||
import { useContext, useEffect } from 'react'
|
||||
import { Context } from './context'
|
||||
|
||||
export default length => {
|
||||
const context = useContext(Context)
|
||||
const { register, index, step } = context
|
||||
useEffect(() => {
|
||||
if (typeof register !== 'function') return
|
||||
register(index, { steps: length })
|
||||
}, [length])
|
||||
|
||||
return step
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
import { useContext } from 'react'
|
||||
import { ThemeContext } from '@emotion/core'
|
||||
|
||||
export default () => useContext(ThemeContext)
|
@ -1,28 +0,0 @@
|
||||
# MDX Deck Export CLI
|
||||
|
||||
```sh
|
||||
npm i -D @mdx-deck/export
|
||||
```
|
||||
|
||||
Export as PDF
|
||||
|
||||
```sh
|
||||
mdx-deck-export pdf deck.mdx
|
||||
```
|
||||
|
||||
Export as PNG
|
||||
|
||||
```sh
|
||||
mdx-deck-export png deck.mdx
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```
|
||||
-d --out-dir Output directory
|
||||
-f --out-file Output filename
|
||||
-p --port Server port
|
||||
-w --width Width in pixels
|
||||
-h --height Height in pixels
|
||||
--no-sandbox Disable puppeteer sandbox
|
||||
```
|
@ -1,68 +0,0 @@
|
||||
const path = require('path')
|
||||
const puppeteer = require('puppeteer')
|
||||
const mkdirp = require('mkdirp')
|
||||
const dev = require('mdx-deck/lib/dev')
|
||||
const findup = require('find-up')
|
||||
|
||||
module.exports = async opts => {
|
||||
const { type, outDir, outFile, port, width, height, sandbox } = opts
|
||||
|
||||
const args = []
|
||||
if (!sandbox) {
|
||||
args.push('--no-sandbox', '--disable-setuid-sandbox')
|
||||
}
|
||||
|
||||
if (opts.webpack) {
|
||||
opts.webpack = require(path.resolve(opts.webpack))
|
||||
} else {
|
||||
const webpackConfig = findup.sync('webpack.config.js', { cwd: opts.dirname })
|
||||
if (webpackConfig) opts.webpack = require(webpackConfig)
|
||||
}
|
||||
|
||||
const server = await dev(opts)
|
||||
|
||||
const browser = await puppeteer.launch({ args })
|
||||
const page = await browser.newPage()
|
||||
const filename = path.join(
|
||||
outDir,
|
||||
path.basename(outFile, path.extname(outFile)) + '.' + type
|
||||
)
|
||||
mkdirp.sync(outDir)
|
||||
|
||||
switch (type) {
|
||||
case 'pdf':
|
||||
const url = `http://localhost:${port}/print`
|
||||
await page.goto(url, {
|
||||
waitUntil: 'networkidle2',
|
||||
})
|
||||
await page.pdf({
|
||||
width,
|
||||
height,
|
||||
path: filename,
|
||||
scale: 1,
|
||||
printBackground: true,
|
||||
})
|
||||
break
|
||||
case 'png':
|
||||
await page.setViewport({ width, height })
|
||||
await page.goto('http://localhost:' + port, {
|
||||
waitUntil: 'networkidle2',
|
||||
})
|
||||
await page.screenshot({
|
||||
path: filename,
|
||||
type: 'png',
|
||||
clip: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
await browser.close()
|
||||
await server.close()
|
||||
|
||||
return filename
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "@mdx-deck/export",
|
||||
"version": "2.5.1",
|
||||
"main": "index.js",
|
||||
"author": "Brent Jackson <jxnblk@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mdx-deck-export": "./cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"pdf": "./cli.js pdf ../../docs/demo.mdx -d ../../docs/dist",
|
||||
"png": "./cli.js png ../../docs/demo.mdx -d ../../docs/dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"mdx-deck": "^2.5.1",
|
||||
"meow": "^5.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"puppeteer": "^1.13.0"
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
.cache
|
||||
public
|
||||
src/pages
|
||||
src/decks
|
@ -1,68 +1,75 @@
|
||||
# @mdx-deck/gatsby-theme
|
||||
|
||||
**WIP** A Gatsby theme for adding MDX Decks to your Gatsby site
|
||||
# gatsby-theme-mdx-deck
|
||||
|
||||
Add MDX Deck presentations to any Gatsby site
|
||||
|
||||
```sh
|
||||
npm i @mdx-deck/gatsby-theme
|
||||
npm i gatsby-theme-mdx-deck
|
||||
```
|
||||
|
||||
_Note:_ This theme **requires MDX v1** and will not work with previous versions of MDX
|
||||
|
||||
```js
|
||||
// gatsby-config.js
|
||||
module.exports = {
|
||||
plugins: ['@mdx-deck/gatsby-theme'],
|
||||
plugins: [
|
||||
'gatsby-theme-mdx-deck',
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Add MDX Decks to the `src/decks/` directory. The filename will be used for the route of that deck.
|
||||
Add one or more MDX presentation files to the `decks/` directory.
|
||||
The filenames will be used for creating routes to each deck.
|
||||
|
||||
**/src/decks/hello.mdx**
|
||||
Example `decks/hello.mdx`
|
||||
|
||||
```mdx
|
||||
# Hello!
|
||||
|
||||
---
|
||||
|
||||
## Beep
|
||||
## Beep boop
|
||||
```
|
||||
|
||||
## Using Layouts
|
||||
## Layouts
|
||||
|
||||
Slide layout components must be rendered inline, _not_ using the default export syntax.
|
||||
Individual slides can be wrapped with layout components,
|
||||
which work similarly to slide templates found in other presentation software.
|
||||
|
||||
**/src/decks/hello.mdx**
|
||||
Example `decks/hello.mdx`
|
||||
|
||||
```mdx
|
||||
import Layout from './my-layout'
|
||||
|
||||
<Layout>
|
||||
|
||||
# Hello Layout
|
||||
# Hello
|
||||
|
||||
</Layout>
|
||||
|
||||
---
|
||||
|
||||
## Beep boop
|
||||
```
|
||||
|
||||
## Theme Config
|
||||
## Configuration Options
|
||||
|
||||
The following options can be passed to the gatsby theme.
|
||||
The Gatsby theme accepts the following options.
|
||||
|
||||
```js
|
||||
// gatsby-config.js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
resolve: '@mdx-deck/gatsby-theme',
|
||||
resolve: 'gatsby-theme-mdx-deck',
|
||||
options: {
|
||||
// disable gatsby-mdx plugin – use this when your site already uses gatsby-mdx
|
||||
// enable or disable gatsby-plugin-mdx
|
||||
mdx: false,
|
||||
// source directory for decks
|
||||
path: 'src/presentations',
|
||||
// name routes' basepath
|
||||
name: 'presentations',
|
||||
},
|
||||
},
|
||||
],
|
||||
// source directory
|
||||
contentPath: 'decks',
|
||||
// base path for routes generate by this theme
|
||||
basePath: ''
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
6
packages/gatsby-theme/decks/beep.mdx
Normal file
6
packages/gatsby-theme/decks/beep.mdx
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
# Beep
|
||||
|
||||
---
|
||||
|
||||
## Boop bop
|
69
packages/gatsby-theme/decks/hello.mdx
Normal file
69
packages/gatsby-theme/decks/hello.mdx
Normal file
@ -0,0 +1,69 @@
|
||||
import { Head, Appear, Notes } from '../src'
|
||||
|
||||
<Head>
|
||||
<link
|
||||
rel='stylesheet'
|
||||
href='https://fonts.googleapis.com/css?family=Roboto+Mono&display=swap'
|
||||
/>
|
||||
</Head>
|
||||
|
||||
# Hello
|
||||
|
||||
---
|
||||
|
||||
`@mdx-deck/gatsby-theme-next`
|
||||
|
||||
<Notes>
|
||||
|
||||
Hi
|
||||
Hello, my secret notes...
|
||||
|
||||
</Notes>
|
||||
|
||||
---
|
||||
|
||||
## Beep
|
||||
|
||||
---
|
||||
|
||||
## Appear:
|
||||
|
||||
<ul>
|
||||
<Appear>
|
||||
<li>One</li>
|
||||
<li>Two</li>
|
||||
<li>Three</li>
|
||||
</Appear>
|
||||
</ul>
|
||||
|
||||
---
|
||||
|
||||
## Hi
|
||||
|
||||
---
|
||||
|
||||
```jsx
|
||||
import React from 'react'
|
||||
import { Embed } from 'mdx-deck'
|
||||
import Hello from './hello.mdx'
|
||||
|
||||
export default props =>
|
||||
<Embed
|
||||
src={Hello}
|
||||
slide={2}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
More steps
|
||||
|
||||
<Appear>
|
||||
<div>beep</div>
|
||||
<div>boop</div>
|
||||
</Appear>
|
||||
|
||||
---
|
||||
|
||||
## The End
|
||||
|
6
packages/gatsby-theme/decks/untitled.mdx
Normal file
6
packages/gatsby-theme/decks/untitled.mdx
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
This deck does not have any headings
|
||||
|
||||
---
|
||||
|
||||
Which means the resolver won't get a title
|
@ -1,43 +1,31 @@
|
||||
const path = require('path')
|
||||
const pkg = require('./package.json')
|
||||
|
||||
const remarkPlugins = [require('remark-emoji'), require('remark-unwrap-images')]
|
||||
|
||||
const IS_LOCAL = process.cwd() === __dirname
|
||||
|
||||
const themeConfig = (opts = {}) => {
|
||||
const { path: source = 'src/decks', name = 'decks', mdx = true } = opts
|
||||
const remarkPlugins = [require('remark-unwrap-images'), require('remark-emoji')]
|
||||
|
||||
const config = (opts = {}) => {
|
||||
const { mdx = true, contentPath: name = 'decks' } = opts
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
'gatsby-plugin-emotion',
|
||||
{
|
||||
resolve: 'gatsby-source-filesystem',
|
||||
options: {
|
||||
name,
|
||||
path: path.resolve(source),
|
||||
path: name,
|
||||
},
|
||||
},
|
||||
mdx && {
|
||||
resolve: 'gatsby-plugin-mdx',
|
||||
options: {
|
||||
extensions: ['.mdx', '.md'],
|
||||
remarkPlugins,
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-plugin-compile-es6-packages',
|
||||
options: {
|
||||
modules: [
|
||||
pkg.name,
|
||||
'@mdx-deck/components',
|
||||
'@mdx-deck/themes',
|
||||
'@mdx-deck/layouts',
|
||||
],
|
||||
},
|
||||
},
|
||||
'gatsby-plugin-react-helmet',
|
||||
'gatsby-plugin-emotion',
|
||||
'gatsby-plugin-catch-links',
|
||||
'gatsby-plugin-theme-ui',
|
||||
].filter(Boolean),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IS_LOCAL ? themeConfig() : themeConfig
|
||||
module.exports = IS_LOCAL ? config() : config
|
||||
|
@ -1,105 +1,186 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { createFilePath } = require('gatsby-source-filesystem')
|
||||
const Debug = require('debug')
|
||||
const mkdirp = require('mkdirp')
|
||||
// based on gatsby-theme-blog
|
||||
const fs = require(`fs`)
|
||||
const path = require(`path`)
|
||||
const mkdirp = require(`mkdirp`)
|
||||
const crypto = require(`crypto`)
|
||||
const Debug = require(`debug`)
|
||||
const pkg = require('./package.json')
|
||||
|
||||
const debug = Debug('@mdx-deck/gatsby-theme')
|
||||
const debug = Debug(pkg.name)
|
||||
|
||||
let basePath
|
||||
let contentPath
|
||||
|
||||
const DeckTemplate = require.resolve(`./src/templates/deck`)
|
||||
const DecksTemplate = require.resolve(`./src/templates/decks`)
|
||||
|
||||
exports.onPreBootstrap = ({ store }, opts = {}) => {
|
||||
const { path: source = 'src/decks' } = opts
|
||||
|
||||
const isDir = fs.statSync(source).isDirectory()
|
||||
const dirname = isDir ? source : path.dirname(source)
|
||||
const { program } = store.getState()
|
||||
const dir = path.join(program.directory, dirname)
|
||||
|
||||
debug(`Initializing ${dir} directory`)
|
||||
mkdirp.sync(dir)
|
||||
basePath = opts.basePath || `/`
|
||||
contentPath = opts.contentPath || `decks`
|
||||
|
||||
if (opts.cli) return
|
||||
const dirname = path.join(program.directory, contentPath)
|
||||
mkdirp.sync(dirname)
|
||||
|
||||
debug(`Initializing ${dirname} directory`)
|
||||
}
|
||||
|
||||
exports.onCreateNode = ({ node, actions, getNode }, opts = {}) => {
|
||||
const { name = 'decks' } = opts
|
||||
if (node.internal.type !== 'Mdx') return
|
||||
|
||||
const value = path.join('/', name, createFilePath({ node, getNode }))
|
||||
actions.createNodeField({
|
||||
name: 'deck',
|
||||
node,
|
||||
value,
|
||||
const mdxResolverPassthrough = fieldName => async (
|
||||
source,
|
||||
args,
|
||||
context,
|
||||
info
|
||||
) => {
|
||||
const type = info.schema.getType(`Mdx`)
|
||||
const mdxNode = context.nodeModel.getNodeById({
|
||||
id: source.parent,
|
||||
})
|
||||
const resolver = type.getFields()[fieldName].resolve
|
||||
const result = await resolver(mdxNode, args, context, {
|
||||
fieldName,
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
const stripSlash = str => str.replace(/\/$/, '')
|
||||
const resolveTitle = async (...args) => {
|
||||
const headings = await mdxResolverPassthrough('headings')(...args)
|
||||
const [first = {}] = headings
|
||||
return first.value || ''
|
||||
}
|
||||
|
||||
exports.createPages = async ({ graphql, actions }, opts = {}) => {
|
||||
const { name = 'decks' } = opts
|
||||
exports.sourceNodes = ({ actions, schema }) => {
|
||||
const { createTypes } = actions
|
||||
createTypes(
|
||||
schema.buildObjectType({
|
||||
name: `Deck`,
|
||||
fields: {
|
||||
id: { type: `ID!` },
|
||||
slug: {
|
||||
type: `String!`,
|
||||
},
|
||||
title: {
|
||||
type: `String!`,
|
||||
resolve: resolveTitle,
|
||||
},
|
||||
body: {
|
||||
type: `String!`,
|
||||
resolve: mdxResolverPassthrough(`body`),
|
||||
},
|
||||
},
|
||||
interfaces: [`Node`],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
exports.createPages = async ({ graphql, actions, reporter }) => {
|
||||
const { createPage } = actions
|
||||
|
||||
const result = await graphql(`
|
||||
{
|
||||
allMdx {
|
||||
allDeck {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
fields {
|
||||
deck
|
||||
}
|
||||
parent {
|
||||
... on File {
|
||||
name
|
||||
sourceInstanceName
|
||||
}
|
||||
}
|
||||
slug
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
if (result.errors) {
|
||||
debug(result.errors)
|
||||
return
|
||||
reporter.panic(result.errors)
|
||||
}
|
||||
|
||||
const decks = result.data.allMdx.edges
|
||||
.filter(edge => {
|
||||
return edge.node.parent.sourceInstanceName === name
|
||||
})
|
||||
.map(edge => edge.node)
|
||||
const { allDeck } = result.data
|
||||
const decks = allDeck.edges
|
||||
|
||||
// single deck mode
|
||||
if (decks.length === 1) {
|
||||
const [deck] = decks
|
||||
const pathname = path.join('/', name)
|
||||
const matchPath = path.join(pathname, '*')
|
||||
actions.createPage({
|
||||
path: pathname,
|
||||
const matchPath = path.join(basePath, '*')
|
||||
createPage({
|
||||
path: basePath,
|
||||
matchPath,
|
||||
component: require.resolve('./src/templates/deck.js'),
|
||||
component: DeckTemplate,
|
||||
context: {
|
||||
id: deck.id,
|
||||
basepath: stripSlash(pathname),
|
||||
...deck.node,
|
||||
slug: '',
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// index page
|
||||
actions.createPage({
|
||||
path: path.join('/', name),
|
||||
component: require.resolve('./src/templates/index.js'),
|
||||
})
|
||||
decks.forEach(({ node }, index) => {
|
||||
const { slug } = node
|
||||
const matchPath = path.join(slug, '*')
|
||||
|
||||
decks.forEach(deck => {
|
||||
const matchPath = path.join(deck.fields.deck, '*')
|
||||
actions.createPage({
|
||||
path: deck.fields.deck,
|
||||
matchPath: path.join(deck.fields.deck, '*'),
|
||||
component: require.resolve('./src/templates/deck.js'),
|
||||
context: {
|
||||
id: deck.id,
|
||||
basepath: stripSlash(deck.fields.deck),
|
||||
},
|
||||
createPage({
|
||||
path: slug,
|
||||
matchPath,
|
||||
component: DeckTemplate,
|
||||
context: node,
|
||||
})
|
||||
createPage({
|
||||
path: slug + '/print',
|
||||
component: DeckTemplate,
|
||||
context: node,
|
||||
})
|
||||
})
|
||||
|
||||
// index page
|
||||
createPage({
|
||||
path: basePath,
|
||||
component: DecksTemplate,
|
||||
context: {
|
||||
decks,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
exports.onCreateNode = ({ node, actions, getNode, createNodeId }) => {
|
||||
const { createNode, createParentChildLink } = actions
|
||||
|
||||
const toPath = node => {
|
||||
const { dir } = path.parse(node.relativePath)
|
||||
return path.join(basePath, dir, node.name)
|
||||
}
|
||||
|
||||
if (node.internal.type !== `Mdx`) return
|
||||
|
||||
const fileNode = getNode(node.parent)
|
||||
const source = fileNode.sourceInstanceName
|
||||
|
||||
if (node.internal.type === `Mdx` && source === contentPath) {
|
||||
const slug = toPath(fileNode)
|
||||
|
||||
createNode({
|
||||
slug,
|
||||
// Required fields.
|
||||
id: createNodeId(`${node.id} >>> Deck`),
|
||||
parent: node.id,
|
||||
children: [],
|
||||
internal: {
|
||||
type: `Deck`,
|
||||
contentDigest: crypto
|
||||
.createHash(`md5`)
|
||||
.update(JSON.stringify({ slug }))
|
||||
.digest(`hex`),
|
||||
content: JSON.stringify({ slug }),
|
||||
description: `Slide Decks`,
|
||||
},
|
||||
})
|
||||
createParentChildLink({ parent: fileNode, child: node })
|
||||
}
|
||||
}
|
||||
|
||||
exports.onCreateDevServer = ({ app }) => {
|
||||
console.log('onCreateDevServer')
|
||||
if (typeof process.send !== 'function') return
|
||||
process.send({
|
||||
mdxDeck: true,
|
||||
})
|
||||
}
|
||||
|
1
packages/gatsby-theme/gatsby-ssr.js
Normal file
1
packages/gatsby-theme/gatsby-ssr.js
Normal file
@ -0,0 +1 @@
|
||||
export { wrapPageElement } from './src'
|
@ -1 +1 @@
|
||||
// noop
|
||||
export * from './src'
|
||||
|
@ -1,36 +1,45 @@
|
||||
{
|
||||
"name": "@mdx-deck/gatsby-theme",
|
||||
"version": "2.5.0",
|
||||
"name": "gatsby-theme-mdx-deck",
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"author": "Brent Jackson <jxnblk@gmail.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "gatsby develop",
|
||||
"build": "gatsby build"
|
||||
"build": "gatsby build",
|
||||
"clean": "gatsby clean"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"gatsby": "^2.3.17",
|
||||
"devDependencies": {
|
||||
"gatsby": "^2.13.6",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gatsby": "^2.3.17",
|
||||
"peerDependencies": {
|
||||
"gatsby": "^2.13.6",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdx-deck/components": "^2.5.0",
|
||||
"@mdx-deck/themes": "^2.5.0",
|
||||
"@mdx-js/mdx": "^1.0.1",
|
||||
"@mdx-js/react": "^1.0.1",
|
||||
"@emotion/core": "^10.0.14",
|
||||
"@mdx-deck/themes": "*",
|
||||
"@mdx-js/mdx": "^1.0.21",
|
||||
"@mdx-js/react": "^1.0.21",
|
||||
"@reach/router": "^1.2.1",
|
||||
"debug": "^4.1.1",
|
||||
"gatsby-plugin-compile-es6-packages": "^1.1.0",
|
||||
"gatsby-plugin-emotion": "^4.0.6",
|
||||
"gatsby-plugin-mdx": "^1.0.6",
|
||||
"gatsby-source-filesystem": "^2.0.29",
|
||||
"gatsby": "^2.13.24",
|
||||
"gatsby-plugin-catch-links": "^2.1.0",
|
||||
"gatsby-plugin-emotion": "^4.1.0",
|
||||
"gatsby-plugin-mdx": "^1.0.13",
|
||||
"gatsby-plugin-react-helmet": "^3.1.0",
|
||||
"gatsby-plugin-theme-ui": "^0.2.6",
|
||||
"gatsby-source-filesystem": "^2.1.3",
|
||||
"hhmmss": "^1.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.merge": "^4.6.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"react-helmet": "^6.0.0-beta",
|
||||
"react-swipeable": "^5.3.0",
|
||||
"remark-emoji": "^2.0.2",
|
||||
"remark-unwrap-images": "^1.0.0"
|
||||
"remark-unwrap-images": "^1.0.0",
|
||||
"theme-ui": "^0.2.14"
|
||||
}
|
||||
}
|
||||
|
35
packages/gatsby-theme/src/components/app.js
Normal file
35
packages/gatsby-theme/src/components/app.js
Normal file
@ -0,0 +1,35 @@
|
||||
import React, { useReducer } from 'react'
|
||||
import merge from 'lodash.merge'
|
||||
import Context from '../context'
|
||||
|
||||
const reducer = (state, next) =>
|
||||
typeof next === 'function'
|
||||
? merge({}, state, next(state))
|
||||
: merge({}, state, next)
|
||||
|
||||
export default props => {
|
||||
const [state, setState] = useReducer(reducer, {
|
||||
mode: 'normal',
|
||||
step: 0,
|
||||
metadata: {},
|
||||
})
|
||||
|
||||
const register = (index, key, value) => {
|
||||
if (state.metadata[index] && state.metadata[index][key]) return
|
||||
setState({
|
||||
metadata: {
|
||||
[index]: {
|
||||
[key]: value,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const context = {
|
||||
...state,
|
||||
setState,
|
||||
register,
|
||||
}
|
||||
|
||||
return <Context.Provider value={context}>{props.children}</Context.Provider>
|
||||
}
|
18
packages/gatsby-theme/src/components/appear.js
Normal file
18
packages/gatsby-theme/src/components/appear.js
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import useSteps from '../hooks/use-steps'
|
||||
|
||||
export const Appear = props => {
|
||||
const children = React.Children.toArray(props.children)
|
||||
const step = useSteps(children.length)
|
||||
const styled = children.map((child, i) =>
|
||||
React.cloneElement(child, {
|
||||
style: {
|
||||
visibility: i < step ? 'visible' : 'hidden',
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
return <>{styled}</>
|
||||
}
|
||||
|
||||
export default Appear
|
19
packages/gatsby-theme/src/components/clock.js
Normal file
19
packages/gatsby-theme/src/components/clock.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export const Clock = props => {
|
||||
const [time, setTime] = useState(new Date().toLocaleTimeString())
|
||||
useEffect(() => {
|
||||
const tick = () => {
|
||||
const now = new Date()
|
||||
setTime(now.toLocaleTimeString())
|
||||
}
|
||||
const timer = setInterval(tick, 1000)
|
||||
return () => {
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return time
|
||||
}
|
||||
|
||||
export default Clock
|
148
packages/gatsby-theme/src/components/deck.js
Normal file
148
packages/gatsby-theme/src/components/deck.js
Normal file
@ -0,0 +1,148 @@
|
||||
import React from 'react'
|
||||
import { Router, globalHistory } from '@reach/router'
|
||||
import { Global } from '@emotion/core'
|
||||
import { ThemeProvider } from 'theme-ui'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import get from 'lodash.get'
|
||||
import merge from 'lodash.merge'
|
||||
import useKeyboard from '../hooks/use-keyboard'
|
||||
import useStorage from '../hooks/use-storage'
|
||||
import useDeck from '../hooks/use-deck'
|
||||
import Context from '../context'
|
||||
import Wrapper from './wrapper'
|
||||
import Slide from './slide'
|
||||
import { modes } from '../constants'
|
||||
|
||||
import Presenter from './presenter'
|
||||
import Overview from './overview'
|
||||
import Grid from './grid'
|
||||
|
||||
const Keyboard = () => {
|
||||
useKeyboard()
|
||||
return false
|
||||
}
|
||||
|
||||
const Storage = () => {
|
||||
useStorage()
|
||||
return false
|
||||
}
|
||||
|
||||
const Print = ({ slides }) => {
|
||||
const outer = useDeck()
|
||||
const context = {
|
||||
...outer,
|
||||
mode: modes.print,
|
||||
}
|
||||
|
||||
return (
|
||||
<Context.Provider value={context}>
|
||||
{slides.map((slide, i) => (
|
||||
<Slide key={i} slide={slide} preview />
|
||||
))}
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const getIndex = () => {
|
||||
const { pathname } = globalHistory.location
|
||||
const paths = pathname.split('/')
|
||||
const n = Number(paths[paths.length - 1])
|
||||
const index = isNaN(n) ? 0 : n
|
||||
return index
|
||||
}
|
||||
|
||||
const GoogleFont = ({ theme }) => {
|
||||
if (!theme.googleFont) return false
|
||||
return (
|
||||
<Helmet>
|
||||
<link rel="stylesheet" href={theme.googleFont} />
|
||||
</Helmet>
|
||||
)
|
||||
}
|
||||
|
||||
const mergeThemes = (...themes) =>
|
||||
themes.reduce(
|
||||
(acc, theme) =>
|
||||
typeof theme === 'function' ? theme(acc) : merge(acc, theme),
|
||||
{}
|
||||
)
|
||||
|
||||
export default ({
|
||||
slides = [],
|
||||
pageContext: { title, slug },
|
||||
theme = {},
|
||||
themes = [],
|
||||
...props
|
||||
}) => {
|
||||
const outer = useDeck()
|
||||
const index = getIndex()
|
||||
|
||||
const head = slides.head.children
|
||||
|
||||
const { components, ...mergedTheme } = mergeThemes(theme, ...themes)
|
||||
|
||||
const context = {
|
||||
...outer,
|
||||
slug,
|
||||
length: slides.length,
|
||||
index,
|
||||
steps: get(outer, `metadata.${index}.steps`),
|
||||
notes: get(outer, `metadata.${index}.notes`),
|
||||
theme: mergedTheme,
|
||||
}
|
||||
|
||||
let Mode = ({ children }) => <React.Fragment children={children} />
|
||||
|
||||
switch (context.mode) {
|
||||
case modes.presenter:
|
||||
Mode = Presenter
|
||||
break
|
||||
case modes.overview:
|
||||
Mode = Overview
|
||||
break
|
||||
case modes.grid:
|
||||
Mode = Grid
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
{head}
|
||||
</Helmet>
|
||||
<GoogleFont theme={mergedTheme} />
|
||||
<Context.Provider value={context}>
|
||||
<ThemeProvider components={components} theme={mergedTheme}>
|
||||
<Global
|
||||
styles={{
|
||||
body: {
|
||||
margin: 0,
|
||||
overflow: context.mode === modes.normal ? 'hidden' : null,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Keyboard />
|
||||
<Storage />
|
||||
<Wrapper>
|
||||
<Mode slides={slides}>
|
||||
<Router
|
||||
basepath={slug}
|
||||
style={{
|
||||
height: '100%',
|
||||
}}>
|
||||
<Slide index={0} path="/" slide={slides[0]} />
|
||||
{slides.map((slide, i) => (
|
||||
<Slide key={i} index={i} path={i + '/*'} slide={slide} />
|
||||
))}
|
||||
<Print path="/print" slides={slides} />
|
||||
</Router>
|
||||
</Mode>
|
||||
</Wrapper>
|
||||
</ThemeProvider>
|
||||
</Context.Provider>
|
||||
</>
|
||||
)
|
||||
}
|
41
packages/gatsby-theme/src/components/decks.js
Normal file
41
packages/gatsby-theme/src/components/decks.js
Normal file
@ -0,0 +1,41 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import { Link } from 'gatsby'
|
||||
|
||||
export default ({ decks }) => {
|
||||
return (
|
||||
<div
|
||||
sx={{
|
||||
fontFamily: 'ui',
|
||||
fontWeight: 'bold',
|
||||
px: 4,
|
||||
py: 3,
|
||||
}}>
|
||||
<h1>MDX Deck</h1>
|
||||
<ul
|
||||
sx={{
|
||||
p: 0,
|
||||
}}>
|
||||
{decks.map(d => (
|
||||
<li
|
||||
key={d.id}
|
||||
sx={{
|
||||
my: 2,
|
||||
}}>
|
||||
<Link
|
||||
to={d.slug}
|
||||
sx={{
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
':hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}}>
|
||||
{d.title || d.slug}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
42
packages/gatsby-theme/src/components/embed.js
Normal file
42
packages/gatsby-theme/src/components/embed.js
Normal file
@ -0,0 +1,42 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import splitSlides from '../split-slides'
|
||||
import Slide from './slide'
|
||||
import Zoom from './zoom'
|
||||
|
||||
const wrapper = ({ slide: i, ratio, zoom, ...props }) => {
|
||||
const slides = splitSlides(props)
|
||||
const slide = slides[i - 1]
|
||||
|
||||
if (!slide) {
|
||||
return <pre>No slide found (slide {i})</pre>
|
||||
}
|
||||
|
||||
return (
|
||||
<Zoom zoom={zoom} ratio={ratio}>
|
||||
<Slide slide={slide} preview />
|
||||
</Zoom>
|
||||
)
|
||||
}
|
||||
|
||||
const components = {
|
||||
wrapper,
|
||||
}
|
||||
|
||||
export const Embed = ({
|
||||
src: Deck,
|
||||
slide = 1,
|
||||
ratio = 16 / 9,
|
||||
zoom = 1,
|
||||
...props
|
||||
}) => (
|
||||
<Deck
|
||||
{...props}
|
||||
components={components}
|
||||
slide={slide}
|
||||
ratio={ratio}
|
||||
zoom={zoom}
|
||||
/>
|
||||
)
|
||||
|
||||
export default Embed
|
21
packages/gatsby-theme/src/components/full-screen-code.js
Normal file
21
packages/gatsby-theme/src/components/full-screen-code.js
Normal file
@ -0,0 +1,21 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
|
||||
export const FullScreenCode = ({ ...props }) => (
|
||||
<div
|
||||
{...props}
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
pre: {
|
||||
// hack for prism styles
|
||||
margin: '0 !important',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
export default FullScreenCode
|
36
packages/gatsby-theme/src/components/grid.js
Normal file
36
packages/gatsby-theme/src/components/grid.js
Normal file
@ -0,0 +1,36 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import { navigate } from '@reach/router'
|
||||
import useDeck from '../hooks/use-deck'
|
||||
import { modes } from '../constants'
|
||||
import SlideList from './slide-list'
|
||||
|
||||
export default ({ slides }) => {
|
||||
const { slug, setState } = useDeck()
|
||||
return (
|
||||
<div
|
||||
sx={{
|
||||
minHeight: '100vh',
|
||||
color: 'white',
|
||||
bg: 'black',
|
||||
}}>
|
||||
<div
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
<SlideList
|
||||
slides={slides}
|
||||
onClick={i => {
|
||||
navigate([slug, i].join('/'))
|
||||
setState({ mode: modes.normal })
|
||||
}}
|
||||
sx={{
|
||||
width: '25%',
|
||||
m: 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
5
packages/gatsby-theme/src/components/head.js
Normal file
5
packages/gatsby-theme/src/components/head.js
Normal file
@ -0,0 +1,5 @@
|
||||
export const Head = props => false
|
||||
|
||||
Head.mdxDeckHead = true
|
||||
|
||||
export default Head
|
26
packages/gatsby-theme/src/components/horizontal.js
Normal file
26
packages/gatsby-theme/src/components/horizontal.js
Normal file
@ -0,0 +1,26 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import React from 'react'
|
||||
|
||||
export const Horizontal = ({ ...props }) => {
|
||||
const children = React.Children.toArray(props.children)
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
{children.map((child, i) => (
|
||||
<div key={child.key} sx={{ width: 100 / children.length + '%' }}>
|
||||
{child}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Horizontal
|
26
packages/gatsby-theme/src/components/image.js
Normal file
26
packages/gatsby-theme/src/components/image.js
Normal file
@ -0,0 +1,26 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
|
||||
export const Image = ({
|
||||
width = '100%',
|
||||
height = '100%',
|
||||
size = 'cover',
|
||||
src,
|
||||
css,
|
||||
...props
|
||||
}) => (
|
||||
<div
|
||||
{...props}
|
||||
sx={{
|
||||
width,
|
||||
height,
|
||||
backgroundImage: `url(${src})`,
|
||||
backgroundSize: size,
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
css={css}
|
||||
/>
|
||||
)
|
||||
|
||||
export default Image
|
23
packages/gatsby-theme/src/components/invert.js
Normal file
23
packages/gatsby-theme/src/components/invert.js
Normal file
@ -0,0 +1,23 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
|
||||
export const Invert = ({ ...props }) => (
|
||||
<div
|
||||
{...props}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
color: 'background',
|
||||
bg: 'text',
|
||||
a: {
|
||||
color: 'inherit',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
export default Invert
|
13
packages/gatsby-theme/src/components/notes.js
Normal file
13
packages/gatsby-theme/src/components/notes.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { useEffect } from 'react'
|
||||
import useDeck from '../hooks/use-deck'
|
||||
|
||||
export const Notes = props => {
|
||||
const context = useDeck()
|
||||
useEffect(() => {
|
||||
context.register(context.index, 'notes', props.children)
|
||||
}, [props.children])
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export default Notes
|
62
packages/gatsby-theme/src/components/overview.js
Normal file
62
packages/gatsby-theme/src/components/overview.js
Normal file
@ -0,0 +1,62 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import { navigate } from '@reach/router'
|
||||
import useDeck from '../hooks/use-deck'
|
||||
import Zoom from './zoom'
|
||||
import SlideList from './slide-list'
|
||||
|
||||
export default ({ slides, children }) => {
|
||||
const { slug, index, length } = useDeck()
|
||||
|
||||
return (
|
||||
<div
|
||||
sx={{
|
||||
display: 'flex',
|
||||
height: '100vh',
|
||||
fontFamily: 'ui',
|
||||
color: 'white',
|
||||
bg: 'black',
|
||||
}}>
|
||||
<div
|
||||
sx={{
|
||||
width: 100 / 6 + '%',
|
||||
minWidth: 0,
|
||||
flex: 'none',
|
||||
height: '100vh',
|
||||
overflowY: 'auto',
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
p: 2,
|
||||
}}>
|
||||
<SlideList
|
||||
slides={slides}
|
||||
zoom={1 / 6}
|
||||
onClick={i => {
|
||||
navigate([slug, i].join('/'))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
sx={{
|
||||
width: 500 / 6 + '%',
|
||||
py: 3,
|
||||
pr: 3,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100vh',
|
||||
}}>
|
||||
<div
|
||||
sx={{
|
||||
flex: '1 1 auto',
|
||||
}}>
|
||||
<Zoom zoom={5 / 6}>{children}</Zoom>
|
||||
</div>
|
||||
<div
|
||||
sx={{
|
||||
py: 3,
|
||||
}}>
|
||||
{index} / {length - 1}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
47
packages/gatsby-theme/src/components/presenter-footer.js
Normal file
47
packages/gatsby-theme/src/components/presenter-footer.js
Normal file
@ -0,0 +1,47 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import React from 'react'
|
||||
import { globalHistory } from '@reach/router'
|
||||
import useDeck from '../hooks/use-deck'
|
||||
import Clock from './clock'
|
||||
import Timer from './timer'
|
||||
|
||||
export default props => {
|
||||
const context = useDeck()
|
||||
const { index, length } = context
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>
|
||||
{index} / {length - 1}
|
||||
</div>
|
||||
<div
|
||||
sx={{
|
||||
mx: 4,
|
||||
}}>
|
||||
<a
|
||||
href={globalHistory.location.href}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
sx={{
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
}}>
|
||||
Open in New Window ↗︎
|
||||
</a>
|
||||
</div>
|
||||
<div sx={{ mx: 'auto' }} />
|
||||
<div
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
mx: 4,
|
||||
}}>
|
||||
<Timer />
|
||||
</div>
|
||||
<div>
|
||||
<Clock />
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
72
packages/gatsby-theme/src/components/presenter.js
Normal file
72
packages/gatsby-theme/src/components/presenter.js
Normal file
@ -0,0 +1,72 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import React from 'react'
|
||||
import Zoom from './zoom'
|
||||
import Slide from './slide'
|
||||
import useDeck from '../hooks/use-deck'
|
||||
import Footer from './presenter-footer'
|
||||
|
||||
export const Presenter = ({ slides, children }) => {
|
||||
const context = useDeck()
|
||||
const next = slides[context.index + 1]
|
||||
const notes = context.notes ? React.Children.toArray(context.notes) : false
|
||||
|
||||
return (
|
||||
<div
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
height: '100vh',
|
||||
fontFamily: 'ui',
|
||||
color: 'white',
|
||||
bg: 'black',
|
||||
}}>
|
||||
<div
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flex: '1 1 auto',
|
||||
height: '60vh',
|
||||
}}>
|
||||
<div
|
||||
sx={{
|
||||
width: '75%',
|
||||
p: 3,
|
||||
}}>
|
||||
<Zoom zoom={3 / 4}>{children}</Zoom>
|
||||
</div>
|
||||
<div
|
||||
sx={{
|
||||
width: '25%',
|
||||
p: 3,
|
||||
}}>
|
||||
<Zoom ratio={4 / 3} zoom={1 / 4}>
|
||||
<Slide slide={next} preview />
|
||||
</Zoom>
|
||||
{notes && (
|
||||
<div
|
||||
sx={{
|
||||
my: 3,
|
||||
}}>
|
||||
{notes}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
sx={{
|
||||
height: 96,
|
||||
p: 3,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: 1,
|
||||
fontWeight: 'bold',
|
||||
fontVariantNumeric: 'tabular-nums',
|
||||
}}>
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Presenter
|
61
packages/gatsby-theme/src/components/slide-list.js
Normal file
61
packages/gatsby-theme/src/components/slide-list.js
Normal file
@ -0,0 +1,61 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import Zoom from './zoom'
|
||||
import Slide from './slide'
|
||||
import useDeck from '../hooks/use-deck'
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
export const SlideList = ({
|
||||
slides = [],
|
||||
ratio = 16 / 9,
|
||||
zoom = 1 / 4,
|
||||
onClick = noop,
|
||||
...props
|
||||
}) => {
|
||||
const { index } = useDeck()
|
||||
const thumb = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
const el = thumb.current
|
||||
if (!el) return
|
||||
if (typeof el.scrollIntoViewIfNeeded === 'function') {
|
||||
el.scrollIntoViewIfNeeded()
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{slides.map((slide, i) => (
|
||||
<div
|
||||
{...props}
|
||||
key={i}
|
||||
role="link"
|
||||
ref={i === index ? thumb : null}
|
||||
onClick={e => {
|
||||
onClick(i)
|
||||
}}
|
||||
style={
|
||||
index === i
|
||||
? {
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
}
|
||||
: null
|
||||
}
|
||||
sx={{
|
||||
m: 2,
|
||||
cursor: 'pointer',
|
||||
outline: index === i ? `4px solid cyan` : null,
|
||||
}}>
|
||||
<Zoom zoom={zoom} ratio={ratio}>
|
||||
<Slide slide={slide} preview />
|
||||
</Zoom>
|
||||
</div>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default SlideList
|
42
packages/gatsby-theme/src/components/slide.js
Normal file
42
packages/gatsby-theme/src/components/slide.js
Normal file
@ -0,0 +1,42 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import React, { Fragment } from 'react'
|
||||
import Context from '../context'
|
||||
import useDeck from '../hooks/use-deck'
|
||||
import useSwipe from '../hooks/use-swipe'
|
||||
import { modes } from '../constants'
|
||||
|
||||
export const Slide = ({ slide, index, preview, ...props }) => {
|
||||
const outer = useDeck()
|
||||
const swipeProps = useSwipe()
|
||||
const context = {
|
||||
...outer,
|
||||
index,
|
||||
preview,
|
||||
}
|
||||
|
||||
return (
|
||||
<Context.Provider value={context}>
|
||||
<div
|
||||
{...(!preview ? swipeProps : {})}
|
||||
sx={{
|
||||
boxSizing: 'border-box',
|
||||
width: '100%',
|
||||
height: context.mode === modes.print ? '100vh' : '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
color: 'text',
|
||||
bg: 'background',
|
||||
variant: 'styles.Slide',
|
||||
}}>
|
||||
{slide}
|
||||
</div>
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default Slide
|
22
packages/gatsby-theme/src/components/split-right.js
Normal file
22
packages/gatsby-theme/src/components/split-right.js
Normal file
@ -0,0 +1,22 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import React from 'react'
|
||||
|
||||
export const SplitRight = ({ children, ...props }) => {
|
||||
const [first, ...rest] = React.Children.toArray(children)
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
<div sx={{ width: '50%' }}>{rest}</div>
|
||||
<div sx={{ width: '50%' }}>{first}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SplitRight
|
22
packages/gatsby-theme/src/components/split.js
Normal file
22
packages/gatsby-theme/src/components/split.js
Normal file
@ -0,0 +1,22 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import React from 'react'
|
||||
|
||||
export const Split = ({ children, ...props }) => {
|
||||
const [first, ...rest] = React.Children.toArray(children)
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
<div sx={{ width: '50%' }}>{first}</div>
|
||||
<div sx={{ width: '50%' }}>{rest}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Split
|
59
packages/gatsby-theme/src/components/timer.js
Normal file
59
packages/gatsby-theme/src/components/timer.js
Normal file
@ -0,0 +1,59 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'theme-ui'
|
||||
import React, { useEffect } from 'react'
|
||||
import hhmmss from 'hhmmss'
|
||||
import useDeck from '../hooks/use-deck'
|
||||
|
||||
let ticker
|
||||
|
||||
export const Timer = props => {
|
||||
const { setState, timer = false, seconds = 0 } = useDeck()
|
||||
|
||||
useEffect(() => {
|
||||
const tick = () => {
|
||||
if (!timer) return
|
||||
setState({
|
||||
seconds: seconds + 1,
|
||||
})
|
||||
}
|
||||
ticker = setInterval(tick, 1000)
|
||||
return () => {
|
||||
clearInterval(ticker)
|
||||
}
|
||||
}, [timer, seconds])
|
||||
|
||||
const toggle = () => {
|
||||
setState({
|
||||
timer: !timer,
|
||||
})
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
setState({ seconds: 0 })
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<button
|
||||
onClick={reset}
|
||||
disabled={!seconds}
|
||||
title="Reset timer"
|
||||
sx={{
|
||||
mx: 1,
|
||||
}}>
|
||||
Reset
|
||||
</button>{' '}
|
||||
<button
|
||||
title={timer ? 'Stop timer' : 'Start timer'}
|
||||
onClick={toggle}
|
||||
sx={{
|
||||
mx: 1,
|
||||
}}>
|
||||
{timer ? 'Stop' : 'Start'}
|
||||
</button>{' '}
|
||||
{hhmmss(seconds)}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default Timer
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user