diff --git a/docs/content/api-reference.md b/docs/content/api-reference.md index 3288d8e51..44141e044 100644 --- a/docs/content/api-reference.md +++ b/docs/content/api-reference.md @@ -174,10 +174,11 @@ Image is a component to display a picture within a slide. It is analogous to an The Markdown components let you include a block of Markdown within a slide using ``, author a complete slide with Markdown using ``, or author a series of slides with Markdown using ``. Markdown tags get converted into Spectacle components. The `---` three dash marker when used inside `` is used to divide content into separate slides. Markdown also supports presenter notes using the `Notes:` marker. `` must be a child of `` where `` and `` are children of ``. -| Props | Type | Example | -| ------------------ | ----------------- | ------------------------------------ | -| `children` | PropTypes.string | `# Hi there` | -| `animateListItems` | PropTypes.boolean | `` | +| Props | Type | Example | +| ------------------ | ----------------- | ----------------------------------------------------------------------------------- | +| `children` | PropTypes.string | `# Hi there` | +| `componentProps` | PropTypes.object | `# I'm purple!` | +| `animateListItems` | PropTypes.boolean | `` | ```jsx diff --git a/examples/js/index.js b/examples/js/index.js index 412ef8e2d..d18867f4f 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -192,9 +192,12 @@ const Presentation = () => ( This is a slide embedded in a div - + {` # This is a Markdown Slide + + - You can pass props down to all elements on the slide. + - Just use the \`componentProps\` prop. `} diff --git a/examples/one-page.html b/examples/one-page.html index 47eb017b6..286abd1ae 100644 --- a/examples/one-page.html +++ b/examples/one-page.html @@ -179,9 +179,25 @@ <${Heading}>This is a slide embedded in a div - <${MarkdownSlide}> + <${MarkdownSlide} componentProps=${{ + color: 'yellow' + }}> ${` # This is a Markdown Slide + + - You can pass props down to all elements on the slide. + - Just use the \`componentProps\` prop. + `} + + <${MarkdownSlide} animateListItems> + ${` + # This is also a Markdown Slide + + It uses the \`animateListItems\` prop. + + - Its list items... + - they will appear in... + - one at a time. `} <${MarkdownSlideSet}> diff --git a/index.d.ts b/index.d.ts index b68b37dd1..45ac800b0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -105,19 +105,24 @@ declare module 'spectacle' { size: number; }>; + type MdComponentProps = { [key: string]: any }; + export const Markdown: React.FC<{ animateListItems?: boolean; children: React.ReactNode; + componentProps?: MdComponentProps; }>; export const MarkdownSlide: React.FC<{ animateListItems?: boolean; children: React.ReactNode; + componentProps?: MdComponentProps; }>; export const MarkdownSlideSet: React.FC<{ animateListItems?: boolean; children: React.ReactNode; + componentProps?: MdComponentProps; }>; export const SpectacleLogo: React.FC<{ diff --git a/src/components/markdown/markdown.js b/src/components/markdown/markdown.js index 50f4fb914..a41625e12 100644 --- a/src/components/markdown/markdown.js +++ b/src/components/markdown/markdown.js @@ -27,7 +27,8 @@ export const Markdown = ({ getPropsForAST: () => {} }, children: rawMarkdownText, - animateListItems = false + animateListItems = false, + componentProps = {} }) => { const { theme: { markdownComponentMap: themeComponentMap = {} } = {} @@ -85,12 +86,19 @@ export const Markdown = ({ CodeBlockComponent ); + const componentMapWithPassedThroughProps = Object.entries( + componentMap + ).reduce((newMap, [key, Component]) => { + newMap[key] = props => ; + return newMap; + }, {}); + // Create the compiler for the _user-visible_ markdown (not presenter notes) const compiler = unified() .use(remark2rehype) .use(rehype2react, { createElement: React.createElement, - components: componentMap + components: componentMapWithPassedThroughProps }); // Compile each of the values we got back from the template function @@ -126,9 +134,10 @@ export const Markdown = ({ }, [ rawMarkdownText, getPropsForAST, - userProvidedComponentMap, themeComponentMap, - animateListItems + userProvidedComponentMap, + animateListItems, + componentProps ]); const { children, ...restProps } = templateProps; @@ -153,11 +162,20 @@ export const MarkdownSlide = ({ componentMap, template, animateListItems = false, + componentProps = {}, ...rest }) => { return ( - + ); }; diff --git a/src/components/markdown/markdown.test.js b/src/components/markdown/markdown.test.js index 4d8d044b5..cc59088c2 100644 --- a/src/components/markdown/markdown.test.js +++ b/src/components/markdown/markdown.test.js @@ -1,10 +1,11 @@ import React from 'react'; import Enzyme, { mount } from 'enzyme'; -import { MarkdownSlide, MarkdownSlideSet } from './markdown'; +import { Markdown, MarkdownSlide, MarkdownSlideSet } from './markdown'; import Adapter from 'enzyme-adapter-react-16'; import Deck from '../deck/deck'; -import { ListItem } from '../typography'; +import { Heading, ListItem } from '../typography'; import Appear from '../appear'; +import Slide from '../slide/slide'; Enzyme.configure({ adapter: new Adapter() }); @@ -80,4 +81,80 @@ describe('', () => { expect(wrapper.find('ul')).toHaveLength(2); expect(wrapper.find(Appear)).toHaveLength(6); }); + + it('Markdown should pass componentProps down to constituent components', () => { + const wrapper = mountInsideDeck( + + Im not styled... + {` + # What's up world, I'm styled. + + - List item + - And another one + `} + + ); + + expect( + wrapper + .find(Heading) + .at(0) + .prop('color') + ).not.toBe('purple'); + + expect( + wrapper + .find(Heading) + .at(1) + .prop('color') + ).toBe('purple'); + + expect( + wrapper + .find(ListItem) + .at(0) + .prop('color') + ).toBe('purple'); + }); + + it('MarkdownSlide should pass componentProps down to constituent components', () => { + const wrapper = mountInsideDeck( + {` + # What's up world, I'm styled. + `} + ); + + expect( + wrapper + .find(Heading) + .at(0) + .prop('color') + ).toBe('purple'); + }); + + it('MarkdownSlideSet should pass componentProps down to constituent components', () => { + const wrapper = mountInsideDeck( + {` + # What's up world, I'm styled. + + --- + + # Another slide + `} + ); + + expect( + wrapper + .find(Heading) + .at(0) + .prop('color') + ).toBe('purple'); + + expect( + wrapper + .find(Heading) + .at(1) + .prop('color') + ).toBe('purple'); + }); });