Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/content/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<Markdown />`, author a complete slide with Markdown using `<MarkdownSlide />`, or author a series of slides with Markdown using `<MarkdownSlides />`. Markdown tags get converted into Spectacle components. The `---` three dash marker when used inside `<MarkdownSlideSet />` is used to divide content into separate slides. Markdown also supports presenter notes using the `Notes:` marker. `<Markdown />` must be a child of `<Slide />` where `<MarkdownSlide />` and `<MarkdownSlideSet />` are children of `<Deck />`.

| Props | Type | Example |
| ------------------ | ----------------- | ------------------------------------ |
| `children` | PropTypes.string | `# Hi there` |
| `animateListItems` | PropTypes.boolean | `<MarkdownSlide animateListItems />` |
| Props | Type | Example |
| ------------------ | ----------------- | ----------------------------------------------------------------------------------- |
| `children` | PropTypes.string | `# Hi there` |
| `componentProps` | PropTypes.object | `<MarkdownSlide componentProps={{ color: 'purple' }}># I'm purple!</MarkdownSlide>` |
| `animateListItems` | PropTypes.boolean | `<MarkdownSlide animateListItems />` |

```jsx
<Slide>
Expand Down
5 changes: 4 additions & 1 deletion examples/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,12 @@ const Presentation = () => (
<Heading>This is a slide embedded in a div</Heading>
</Slide>
</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>
<MarkdownSlide animateListItems>
Expand Down
18 changes: 17 additions & 1 deletion examples/one-page.html
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,25 @@
<${Heading}>This is a slide embedded in a div</${Heading}>
</${Slide}>
</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}>
<${MarkdownSlide} animateListItems>
${`
# This is also a Markdown Slide

It uses the \`animateListItems\` prop.

- Its list items...
- they will appear in...
- one at a time.
`}
</${MarkdownSlide}>
<${MarkdownSlideSet}>
Expand Down
5 changes: 5 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,24 @@ declare module 'spectacle' {
size: number;
}>;

type MdComponentProps = { [key: string]: any };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not thrilled about how loose this type is, but technically any props from valid MD components can be passed here, and the user can supply their own component map (in which case, we can't account for the prop types their components will accept). Seems like it'd be hard to make this as strict as possible - and making this unnecessarily strict is also a bad idea (since TS will yell about it not recognizing props that might actually be valid).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think leaving it open gives us API flexibility. We could also use the Record type but I think that's two sides of the same coin here.


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<{
Expand Down
28 changes: 23 additions & 5 deletions src/components/markdown/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export const Markdown = ({
getPropsForAST: () => {}
},
children: rawMarkdownText,
animateListItems = false
animateListItems = false,
componentProps = {}
}) => {
const {
theme: { markdownComponentMap: themeComponentMap = {} } = {}
Expand Down Expand Up @@ -85,12 +86,19 @@ export const Markdown = ({
CodeBlockComponent
);

const componentMapWithPassedThroughProps = Object.entries(
componentMap
).reduce((newMap, [key, Component]) => {
newMap[key] = props => <Component {...props} {...componentProps} />;
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
Expand Down Expand Up @@ -126,9 +134,10 @@ export const Markdown = ({
}, [
rawMarkdownText,
getPropsForAST,
userProvidedComponentMap,
themeComponentMap,
animateListItems
userProvidedComponentMap,
animateListItems,
componentProps
]);

const { children, ...restProps } = templateProps;
Expand All @@ -153,11 +162,20 @@ export const MarkdownSlide = ({
componentMap,
template,
animateListItems = false,
componentProps = {},
...rest
}) => {
return (
<Slide {...rest}>
<Markdown {...{ componentMap, template, animateListItems, children }} />
<Markdown
{...{
componentMap,
template,
animateListItems,
componentProps,
children
}}
/>
</Slide>
);
};
Expand Down
81 changes: 79 additions & 2 deletions src/components/markdown/markdown.test.js
Original file line number Diff line number Diff line change
@@ -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() });

Expand Down Expand Up @@ -80,4 +81,80 @@ describe('<MarkdownSlideSet />', () => {
expect(wrapper.find('ul')).toHaveLength(2);
expect(wrapper.find(Appear)).toHaveLength(6);
});

it('Markdown should pass componentProps down to constituent components', () => {
const wrapper = mountInsideDeck(
<Slide>
<Heading>Im not styled...</Heading>
<Markdown componentProps={{ color: 'purple' }}>{`
# What's up world, I'm styled.

- List item
- And another one
`}</Markdown>
</Slide>
);

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(
<MarkdownSlide componentProps={{ color: 'purple' }}>{`
# What's up world, I'm styled.
`}</MarkdownSlide>
);

expect(
wrapper
.find(Heading)
.at(0)
.prop('color')
).toBe('purple');
});

it('MarkdownSlideSet should pass componentProps down to constituent components', () => {
const wrapper = mountInsideDeck(
<MarkdownSlideSet componentProps={{ color: 'purple' }}>{`
# What's up world, I'm styled.

---

# Another slide
`}</MarkdownSlideSet>
);

expect(
wrapper
.find(Heading)
.at(0)
.prop('color')
).toBe('purple');

expect(
wrapper
.find(Heading)
.at(1)
.prop('color')
).toBe('purple');
});
});