├── .gitignore ├── cm-night-owl.css ├── components ├── about-me.js ├── accordion-code.js ├── accordion │ ├── above.js │ ├── base.js │ ├── implementations.js │ ├── left.js │ ├── prevent-close.js │ ├── right.js │ ├── single-prevent-close.js │ ├── single.js │ └── standard.js ├── aprocalypse.js ├── code-block.js ├── demo-layout.js ├── demo-slide.js ├── first-slide.js ├── randomly-placed.js ├── resources.js ├── shared.js ├── sob.js ├── tabs │ ├── above.js │ ├── base.js │ ├── implementations.js │ └── standard.js └── white-layout.js ├── deck.mdx ├── package-lock.json ├── package.json ├── public ├── apropcalypse.png ├── cost-benefits.jpg ├── cry.png ├── epic-react.png ├── erased-from-existence.mp4 ├── sob.png ├── standard │ ├── 3-minutes-with-kent.png │ ├── ama.png │ ├── astronaut.png │ ├── big-smile.png │ ├── books.png │ ├── boy.png │ ├── dash-race-car.png │ ├── dash-reverse.png │ ├── dog.png │ ├── eggheadio.png │ ├── fem.png │ ├── gde.png │ ├── girl.png │ ├── github.png │ ├── house.png │ ├── kcd-news.png │ ├── medium.png │ ├── neutral-face.png │ ├── office.png │ ├── racing-car-reverse.png │ ├── smile.png │ ├── speaking.png │ ├── trophy.png │ ├── twitch.png │ ├── twitter.svg │ ├── wave.png │ ├── woman.png │ ├── workshopme.png │ └── youtube.png ├── sweat-smile.png └── what-if.jpg ├── script.js ├── styles.css └── theme.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /cm-night-owl.css: -------------------------------------------------------------------------------- 1 | /* 2 | Name: night-owl 1.1.1 3 | Author: Sarah Drasner (https://github.com/sdras) 4 | Original VS Code theme (https://github.com/sdras/night-owl-vscode-theme) 5 | copied from https://github.com/dawnlabs/carbon/blob/master/lib/custom/themes/night-owl.css 6 | */ 7 | /* basic */ 8 | .CodeMirror.cm-s-night-owl { 9 | font-family: 'Dank Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace; 10 | font-weight: 350; 11 | color: #abb2bf; 12 | background-color: #011627; 13 | } 14 | .cm-s-night-owl .CodeMirror-selected { 15 | background-color: #1d3b53 !important; 16 | } 17 | .cm-s-night-owl .CodeMirror-gutter, 18 | .cm-s-night-owl .CodeMirror-gutters { 19 | border: none; 20 | background-color: #011627; 21 | } 22 | .cm-s-night-owl .CodeMirror-linenumber, 23 | .cm-s-night-owl .CodeMirror-linenumbers { 24 | color: #5f7e97 !important; 25 | background-color: transparent; 26 | } 27 | .cm-s-night-owl .CodeMirror-lines { 28 | color: #abb2bf !important; 29 | background-color: transparent; 30 | } 31 | .cm-s-night-owl .CodeMirror-cursor { 32 | border-left: 2px solid #7e57c2 !important; 33 | } 34 | /* addon: edit/machingbrackets.js & addon: edit/matchtags.js */ 35 | .cm-s-night-owl .CodeMirror-matchingbracket, 36 | .cm-s-night-owl .CodeMirror-matchingtag { 37 | border-bottom: 2px solid #c792ea; 38 | color: #abb2bf !important; 39 | background-color: transparent; 40 | } 41 | .cm-s-night-owl .CodeMirror-nonmatchingbracket { 42 | border-bottom: 2px solid #e06c75; 43 | color: #abb2bf !important; 44 | background-color: transparent; 45 | } 46 | /* addon: fold/foldgutter.js */ 47 | .cm-s-night-owl .CodeMirror-foldmarker, 48 | .cm-s-night-owl .CodeMirror-foldgutter, 49 | .cm-s-night-owl .CodeMirror-foldgutter-open, 50 | .cm-s-night-owl .CodeMirror-foldgutter-folded { 51 | border: none; 52 | text-shadow: none; 53 | color: #5c6370 !important; 54 | background-color: transparent; 55 | } 56 | /* addon: selection/active-line.js */ 57 | .cm-s-night-owl .CodeMirror-activeline-background { 58 | background-color: #01121f; 59 | } 60 | /* basic syntax */ 61 | .cm-s-night-owl .cm-quote { 62 | color: #5c6370; 63 | font-style: italic; 64 | } 65 | .cm-s-night-owl .cm-negative { 66 | color: #e06c75; 67 | } 68 | .cm-s-night-owl .cm-positive { 69 | color: #e06c75; 70 | } 71 | .cm-s-night-owl .cm-strong { 72 | color: #f78c6c; 73 | font-weight: bold; 74 | } 75 | .cm-s-night-owl .cm-em { 76 | color: #c792ea; 77 | font-style: italic; 78 | } 79 | .cm-s-night-owl .cm-attribute { 80 | color: #f78c6c; 81 | } 82 | .cm-s-night-owl .cm-link { 83 | color: #ecc48d; 84 | border-bottom: solid 1px #ecc48d; 85 | } 86 | .cm-s-night-owl .cm-keyword { 87 | color: #c792ea; 88 | font-style: italic; 89 | } 90 | .cm-s-night-owl .cm-def { 91 | color: #82aaff; 92 | } 93 | .cm-s-night-owl .cm-atom { 94 | color: #f78c6c; 95 | } 96 | .cm-s-night-owl .cm-number { 97 | color: #f78c6c; 98 | } 99 | .cm-s-night-owl .cm-property { 100 | color: #fff; 101 | } 102 | .cm-s-night-owl .cm-qualifier { 103 | color: #f78c6c; 104 | } 105 | .cm-s-night-owl .cm-variable { 106 | color: #82aaff; 107 | } 108 | .cm-s-night-owl .cm-variable-2 { 109 | color: #82aaff; 110 | } 111 | .cm-s-night-owl .cm-string { 112 | color: #ecc48d; 113 | } 114 | .cm-s-night-owl .cm-string-2 { 115 | color: #addb67; 116 | } 117 | .cm-s-night-owl .cm-operator { 118 | color: #c792ea; 119 | } 120 | .cm-s-night-owl .cm-meta { 121 | color: #7fdbca; 122 | } 123 | .cm-s-night-owl .cm-comment { 124 | color: #5c6370; 125 | font-style: italic; 126 | } 127 | .cm-s-night-owl .cm-error { 128 | color: #e06c75; 129 | } 130 | 131 | .cm-s-night-owl .cm-tag { 132 | color: #addb67; 133 | } 134 | 135 | .cm-s-night-owl .cm-tag.cm-bracket { 136 | color: #7fdbca; 137 | } 138 | -------------------------------------------------------------------------------- /components/about-me.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.div` 5 | display: flex; 6 | justify-content: space-around; 7 | ` 8 | 9 | const IconImage = styled.img` 10 | max-height: 70px; 11 | max-width: 70px; 12 | ` 13 | 14 | function LinkedIconImage({link, name}) { 15 | return ( 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | const IconImageContainer = styled.div` 23 | display: flex; 24 | flex-direction: column; 25 | align-items: center; 26 | ` 27 | 28 | const Family = styled.div` 29 | display: flex; 30 | ` 31 | 32 | const LogoRow = styled(Container)` 33 | margin-top: 30px; 34 | margin-bottom: 30px; 35 | ` 36 | 37 | class AboutMe extends React.Component { 38 | render() { 39 | return ( 40 |
41 | 42 | 43 | 47 | Utah 48 | 49 | 50 | 51 | 55 | 59 | 63 | 67 | 71 | 75 | 76 | wife, 4 kids, & a dog 77 | 78 | 79 | 83 | PayPal 84 | 85 | 86 | 87 | 91 | 92 | 96 | 100 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 |
115 | ) 116 | } 117 | } 118 | 119 | export {AboutMe} 120 | -------------------------------------------------------------------------------- /components/accordion-code.js: -------------------------------------------------------------------------------- 1 | export const accordionCode = ` 2 | const actionTypes = {toggle_index: 'toggle_index'} 3 | 4 | function accordionReducer(openIndexes, action) { 5 | switch (action.type) { 6 | case actionTypes.toggle_index: { 7 | const closing = openIndexes.includes(action.index) 8 | return closing 9 | ? openIndexes.filter(i => i !== action.index) 10 | : [...openIndexes, action.index] 11 | } 12 | default: { 13 | throw new Error('Unhandled type in accordionReducer: ' + action.type) 14 | } 15 | } 16 | } 17 | 18 | function useAccordion({reducer = accordionReducer} = {}) { 19 | const [openIndexes, dispatch] = React.useReducer(reducer, [0]) 20 | const toggleIndex = index => dispatch({type: actionTypes.toggle_index, index}) 21 | return {openIndexes, toggleIndex} 22 | } 23 | 24 | export {useAccordion, accordionReducer, actionTypes} 25 | `.trim() 26 | -------------------------------------------------------------------------------- /components/accordion/above.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Accordion} from './base' 3 | import {AccordionButton, AccordionItem, AccordionContents} from '../shared' 4 | 5 | function AboveAccordion({items, ...props}) { 6 | return ( 7 | 8 | {({openIndexes, handleItemClick}) => ( 9 |
10 | {items.map((item, index) => ( 11 | 12 | 13 | {item.contents} 14 | 15 | handleItemClick(index)} 18 | > 19 | {item.title}{' '} 20 | {openIndexes.includes(index) ? '👆' : '👈'} 21 | 22 | 23 | ))} 24 |
25 | )} 26 |
27 | ) 28 | } 29 | 30 | export {AboveAccordion} 31 | -------------------------------------------------------------------------------- /components/accordion/base.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class Accordion extends React.Component { 4 | static defaultProps = { 5 | stateReducer: (state, changes) => changes, 6 | onStateChange: () => {}, 7 | } 8 | state = {openIndexes: [0]} 9 | getState(state = this.state) { 10 | return { 11 | openIndexes: 12 | this.props.openIndexes === undefined 13 | ? state.openIndexes 14 | : this.props.openIndexes, 15 | } 16 | } 17 | internalSetState(changes, callback = () => {}) { 18 | let allChanges 19 | this.setState( 20 | state => { 21 | const actualState = this.getState(state) 22 | const changesObject = 23 | typeof changes === 'function' ? changes(actualState) : changes 24 | allChanges = this.props.stateReducer(actualState, changesObject) 25 | return allChanges 26 | }, 27 | () => { 28 | this.props.onStateChange(allChanges) 29 | callback() 30 | }, 31 | ) 32 | } 33 | handleItemClick = index => { 34 | this.internalSetState(state => { 35 | const closing = state.openIndexes.includes(index) 36 | return { 37 | type: closing ? 'closing' : 'opening', 38 | openIndexes: closing 39 | ? state.openIndexes.filter(i => i !== index) 40 | : [...state.openIndexes, index], 41 | } 42 | }) 43 | } 44 | render() { 45 | return this.props.children({ 46 | openIndexes: this.getState().openIndexes, 47 | handleItemClick: this.handleItemClick, 48 | }) 49 | } 50 | } 51 | 52 | export {Accordion} 53 | -------------------------------------------------------------------------------- /components/accordion/implementations.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {StandardAccordion} from './standard' 3 | import {AboveAccordion} from './above' 4 | import {RightAccordion} from './right' 5 | import {LeftAccordion} from './left' 6 | import {SingleAccordion} from './single' 7 | import {PreventCloseAccordion} from './prevent-close' 8 | import {SinglePreventCloseAccordion} from './single-prevent-close' 9 | 10 | const items = [ 11 | { 12 | title: '🐴', 13 | contents: ( 14 |
15 | Horses can sleep both lying down and standing up. Domestic horses have a 16 | lifespan of around 25 years. A 19th century horse named 'Old Billy' is 17 | said to have lived 62 years. 18 |
19 | ), 20 | }, 21 | { 22 | title: '🦏', 23 | contents: ( 24 |
25 | Rhino skin maybe thick but it can be quite sensitive to sunburns and 26 | insect bites which is why they like wallow so much – when the mud dries 27 | it acts as protection from the sunburns and insects. 28 |
29 | ), 30 | }, 31 | { 32 | title: '🦄', 33 | contents: ( 34 |
35 | If you’re looking to hunt a unicorn, but don’t know where to begin, try 36 | Lake Superior State University in Sault Ste. Marie, Michigan. Since 37 | 1971, the university has issued permits to unicorn questers. 38 |
39 | ), 40 | }, 41 | ] 42 | 43 | function asImpl(Comp) { 44 | return () => ( 45 |
53 | 54 |
55 | ) 56 | } 57 | 58 | export const Standard = asImpl(StandardAccordion) 59 | export const Above = asImpl(AboveAccordion) 60 | export const Right = asImpl(RightAccordion) 61 | export const Left = asImpl(LeftAccordion) 62 | export const Single = asImpl(SingleAccordion) 63 | export const PreventClose = asImpl(PreventCloseAccordion) 64 | export const SinglePreventClose = asImpl(SinglePreventCloseAccordion) 65 | -------------------------------------------------------------------------------- /components/accordion/left.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Accordion} from './base' 3 | import {css, AccordionButton, AccordionItem, AccordionContents} from '../shared' 4 | 5 | function LeftAccordion({items, ...props}) { 6 | return ( 7 | 8 | {({openIndexes, handleItemClick}) => ( 9 |
10 | {items.map((item, index) => ( 11 | 12 | 13 | {item.contents} 14 | 15 | handleItemClick(index)} 18 | > 19 | {openIndexes.includes(index) ? '👈' : '👉'}{' '} 20 | {item.title} 21 | 22 | 23 | ))} 24 |
25 | )} 26 |
27 | ) 28 | } 29 | 30 | export {LeftAccordion} 31 | -------------------------------------------------------------------------------- /components/accordion/prevent-close.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Accordion} from './base' 3 | import { 4 | AccordionButton, 5 | AccordionItem, 6 | AccordionContents, 7 | preventClose, 8 | } from '../shared' 9 | 10 | function PreventCloseAccordion({items, ...props}) { 11 | return ( 12 | 13 | {({openIndexes, handleItemClick}) => ( 14 |
15 | {items.map((item, index) => ( 16 | 17 | handleItemClick(index)} 20 | > 21 | {item.title}{' '} 22 | {openIndexes.includes(index) ? '👇' : '👈'} 23 | 24 | 25 | {item.contents} 26 | 27 | 28 | ))} 29 |
30 | )} 31 |
32 | ) 33 | } 34 | 35 | export {PreventCloseAccordion} 36 | -------------------------------------------------------------------------------- /components/accordion/right.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Accordion} from './base' 3 | import {css, AccordionButton, AccordionItem, AccordionContents} from '../shared' 4 | 5 | function RightAccordion({items, ...props}) { 6 | return ( 7 | 8 | {({openIndexes, handleItemClick}) => ( 9 |
10 | {items.map((item, index) => ( 11 | 12 | handleItemClick(index)} 15 | > 16 | {openIndexes.includes(index) ? '👉' : '👈'}{' '} 17 | {item.title} 18 | 19 | 20 | {item.contents} 21 | 22 | 23 | ))} 24 |
25 | )} 26 |
27 | ) 28 | } 29 | 30 | export {RightAccordion} 31 | -------------------------------------------------------------------------------- /components/accordion/single-prevent-close.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Accordion} from './base' 3 | import { 4 | AccordionButton, 5 | AccordionItem, 6 | AccordionContents, 7 | single, 8 | preventClose, 9 | combineReducers, 10 | } from '../shared' 11 | 12 | function SinglePreventCloseAccordion({items, ...props}) { 13 | return ( 14 | 15 | {({openIndexes, handleItemClick}) => ( 16 |
17 | {items.map((item, index) => ( 18 | 19 | handleItemClick(index)} 22 | > 23 | {item.title}{' '} 24 | {openIndexes.includes(index) ? '👇' : '👈'} 25 | 26 | 27 | {item.contents} 28 | 29 | 30 | ))} 31 |
32 | )} 33 |
34 | ) 35 | } 36 | 37 | export {SinglePreventCloseAccordion} 38 | -------------------------------------------------------------------------------- /components/accordion/single.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Accordion} from './base' 3 | import { 4 | AccordionButton, 5 | AccordionItem, 6 | AccordionContents, 7 | single, 8 | } from '../shared' 9 | 10 | function SingleAccordion({items, ...props}) { 11 | return ( 12 | 13 | {({openIndexes, handleItemClick}) => ( 14 |
15 | {items.map((item, index) => ( 16 | 17 | handleItemClick(index)} 20 | > 21 | {item.title}{' '} 22 | {openIndexes.includes(index) ? '👇' : '👈'} 23 | 24 | 25 | {item.contents} 26 | 27 | 28 | ))} 29 |
30 | )} 31 |
32 | ) 33 | } 34 | 35 | export {SingleAccordion} 36 | -------------------------------------------------------------------------------- /components/accordion/standard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Accordion} from './base' 3 | import {AccordionButton, AccordionItem, AccordionContents} from '../shared' 4 | 5 | function StandardAccordion({items, ...props}) { 6 | return ( 7 | 8 | {({openIndexes, handleItemClick}) => ( 9 |
10 | {items.map((item, index) => ( 11 | 12 | handleItemClick(index)} 15 | > 16 | {item.title}{' '} 17 | {openIndexes.includes(index) ? '👇' : '👈'} 18 | 19 | 20 | {item.contents} 21 | 22 | 23 | ))} 24 |
25 | )} 26 |
27 | ) 28 | } 29 | 30 | export {StandardAccordion} 31 | -------------------------------------------------------------------------------- /components/aprocalypse.js: -------------------------------------------------------------------------------- 1 | export const aprocalypse = ` 2 | const accordionItems = [ 3 | { 4 | title: '🐴', 5 | openTitle: '🐎', 6 | contents: '...', 7 | disabled: false, 8 | }, 9 | // ... 10 | ] 11 | 12 | const ui = ( 13 | 28 | ) 29 | `.trim() 30 | -------------------------------------------------------------------------------- /components/code-block.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {UnControlled as CodeMirror} from 'react-codemirror2' 3 | 4 | export const CodeBlock = ({children, options = {}, style, ...props}) => ( 5 |
6 | 17 |
18 | ) 19 | -------------------------------------------------------------------------------- /components/demo-layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default ({children}) => ( 4 |
15 | {children} 16 |
17 | ) 18 | -------------------------------------------------------------------------------- /components/demo-slide.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Appear} from 'mdx-deck' 3 | import {CodeBlock} from './code-block' 4 | 5 | function DemoSlide({accordion, followCode, code}) { 6 | return ( 7 |
8 |
9 |
{accordion}
10 |
11 | 12 |
13 | {code.trim()} 14 |
15 | {followCode ? ( 16 |
17 | 25 | {followCode.trim()} 26 | 27 |
28 | ) : null} 29 |
30 |
31 | ) 32 | } 33 | 34 | export {DemoSlide} 35 | -------------------------------------------------------------------------------- /components/first-slide.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.div` 5 | display: flex; 6 | justify-content: space-around; 7 | align-items: center; 8 | ` 9 | 10 | const IconImage = styled.img` 11 | max-height: 70px; 12 | max-width: 70px; 13 | ` 14 | 15 | function LinkedIconImage({link, name}) { 16 | return ( 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | const IconImageContainer = styled.div` 24 | display: flex; 25 | flex-direction: column; 26 | align-items: center; 27 | ` 28 | 29 | const Family = styled.div` 30 | display: flex; 31 | ` 32 | 33 | const LogoRow = styled(Container)` 34 | margin-top: 30px; 35 | margin-bottom: 30px; 36 | ` 37 | 38 | function FirstSlide({title, subtitle}) { 39 | return ( 40 |
53 |

{title}

54 |

{subtitle}

55 | Kent C. Dodds 56 | 57 |
58 | ) 59 | } 60 | 61 | class AboutMe extends React.Component { 62 | render() { 63 | return ( 64 |
65 | 66 | 67 | 71 | TestingJavaScript.com 72 | 73 | 77 | @kentcdodds 78 | 79 | 80 | 84 | EpicReact.Dev 85 | 86 | 87 |
88 | ) 89 | } 90 | } 91 | 92 | export {FirstSlide} 93 | -------------------------------------------------------------------------------- /components/randomly-placed.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function RandomlyPlaced({children, style, ...props}) { 4 | return ( 5 |
6 | {children ? ( 7 |
15 | {children} 16 |
17 | ) : null} 18 |
19 | ) 20 | } 21 | 22 | export {RandomlyPlaced} 23 | -------------------------------------------------------------------------------- /components/resources.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'react-emotion' 3 | 4 | const Title = styled('strong')({ 5 | fontSize: 32, 6 | }) 7 | 8 | const Link = styled('a')({ 9 | fontSize: 24, 10 | color: '#addb67', 11 | }) 12 | Link.defaultProps = {target: '_blank'} 13 | 14 | export const Resources = () => ( 15 | 19 | ) 20 | -------------------------------------------------------------------------------- /components/shared.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import posed from 'react-pose' 3 | import {css as emoCSS} from 'emotion' 4 | import styled from 'react-emotion' 5 | 6 | const css = (...args) => ({className: emoCSS(...args)}) 7 | 8 | const AccordionButton = styled('button')( 9 | { 10 | textAlign: 'left', 11 | minWidth: 80, 12 | cursor: 'pointer', 13 | flex: 1, 14 | paddingTop: 10, 15 | paddingBottom: 10, 16 | fontSize: 20, 17 | border: 'none', 18 | backgroundColor: 'unset', 19 | ':focus': { 20 | outline: 'none', 21 | backgroundColor: 'rgba(255, 255, 255, 0.4)', 22 | }, 23 | }, 24 | ({isOpen}) => 25 | isOpen 26 | ? { 27 | backgroundColor: 'rgba(255, 255, 255, 0.2)', 28 | } 29 | : null, 30 | ) 31 | 32 | const PoseAccordionContents = posed.div({ 33 | open: {maxHeight: 200}, 34 | closed: {maxHeight: 0}, 35 | }) 36 | 37 | function AccordionContents({isOpen, ...props}) { 38 | return ( 39 | 44 | ) 45 | } 46 | 47 | const AccordionItem = styled('div')( 48 | { 49 | display: 'grid', 50 | gridTemplate: 'auto auto', 51 | gridGap: 4, 52 | gridAutoFlow: 'row', 53 | }, 54 | props => ({ 55 | gridAutoFlow: props.direction === 'horizontal' ? 'column' : 'row', 56 | }), 57 | ) 58 | 59 | const TabButtons = styled('div')({display: 'flex'}) 60 | const TabButton = styled(AccordionButton)({ 61 | textAlign: 'center', 62 | }) 63 | const TabItems = styled('div')({ 64 | position: 'relative', 65 | minHeight: 160, 66 | }) 67 | 68 | const BelowTabItem = posed.div({ 69 | open: {opacity: 1, top: 0}, 70 | closed: {opacity: 0, top: 30}, 71 | }) 72 | 73 | const AboveTabItem = posed.div({ 74 | open: {opacity: 1, bottom: 0}, 75 | closed: {opacity: 0, bottom: 30}, 76 | }) 77 | 78 | function TabItem({position, isOpen, ...props}) { 79 | props = { 80 | pose: isOpen ? 'open' : 'closed', 81 | ...css({position: 'absolute', overflowY: 'hidden'}), 82 | ...props, 83 | } 84 | return position === 'above' ? ( 85 | 86 | ) : ( 87 | 88 | ) 89 | } 90 | 91 | function preventClose(state, changes) { 92 | if (changes.type === 'closing' && state.openIndexes.length < 2) { 93 | return {...changes, openIndexes: state.openIndexes} 94 | } 95 | return changes 96 | } 97 | 98 | function single(state, changes) { 99 | if (changes.type === 'opening') { 100 | return {openIndexes: changes.openIndexes.slice(-1)} 101 | } 102 | return changes 103 | } 104 | 105 | function combineReducers(...reducers) { 106 | return (state, changes) => { 107 | for (let reducer of reducers) { 108 | const result = reducer(state, changes) 109 | if (result !== changes) { 110 | return result 111 | } 112 | } 113 | return changes 114 | } 115 | } 116 | 117 | export { 118 | css, 119 | AccordionButton, 120 | AccordionItem, 121 | AccordionContents, 122 | AboveTabItem, 123 | BelowTabItem, 124 | TabItem, 125 | TabItems, 126 | TabButton, 127 | TabButtons, 128 | combineReducers, 129 | preventClose, 130 | single, 131 | } 132 | -------------------------------------------------------------------------------- /components/sob.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Appear} from 'mdx-deck' 3 | import {RandomlyPlaced} from './randomly-placed' 4 | 5 | export const Sob = ({children, style}) => ( 6 |
7 | 8 | {children ? ( 9 |
16 | {children} 17 |
18 | ) : null} 19 |
20 | ) 21 | 22 | export const Sobbing = () => ( 23 |
24 | 25 |
26 | bundle size/perf 😵 27 |
28 |
29 | maintenance overhead 😖 30 |
31 |
32 | 33 | implementation complexity 🐛 34 | 35 |
36 |
37 | API complexity 😕 38 |
39 |
40 | 41 | 42 |
43 |
44 |
58 | 59 | 60 | 61 |
62 |
63 |
64 |
65 | ) 66 | -------------------------------------------------------------------------------- /components/tabs/above.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Tabs} from './base' 3 | import {TabItem, TabItems, TabButtons, TabButton} from '../shared' 4 | 5 | function AboveTabs({items}) { 6 | return ( 7 | 8 | {({openIndexes, handleItemClick}) => ( 9 |
10 | 11 | {items.map((item, index) => ( 12 | 17 | {items[index].contents} 18 | 19 | ))} 20 | 21 | 22 | {items.map((item, index) => ( 23 | handleItemClick(index)} 27 | > 28 | {item.title} 29 | 30 | ))} 31 | 32 |
33 | )} 34 |
35 | ) 36 | } 37 | 38 | export {AboveTabs} 39 | -------------------------------------------------------------------------------- /components/tabs/base.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Accordion} from '../accordion/base' 3 | import {single, preventClose, combineReducers} from '../shared' 4 | 5 | function Tabs({stateReducer = (state, changes) => changes, ...props}) { 6 | return ( 7 | 11 | ) 12 | } 13 | 14 | export {Tabs} 15 | -------------------------------------------------------------------------------- /components/tabs/implementations.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {StandardTabs} from './standard' 3 | import {AboveTabs} from './above' 4 | 5 | const items = [ 6 | { 7 | title: '🐴', 8 | contents: ( 9 |
10 | Horses can sleep both lying down and standing up. Domestic horses have a 11 | lifespan of around 25 years. A 19th century horse named 'Old Billy' is 12 | said to have lived 62 years. 13 |
14 | ), 15 | }, 16 | { 17 | title: '🦏', 18 | contents: ( 19 |
20 | Rhino skin maybe thick but it can be quite sensitive to sunburns and 21 | insect bites which is why they like wallow so much – when the mud dries 22 | it acts as protection from the sunburns and insects. 23 |
24 | ), 25 | }, 26 | { 27 | title: '🦄', 28 | contents: ( 29 |
30 | If you’re looking to hunt a unicorn, but don’t know where to begin, try 31 | Lake Superior State University in Sault Ste. Marie, Michigan. Since 32 | 1971, the university has issued permits to unicorn questers. 33 |
34 | ), 35 | }, 36 | ] 37 | 38 | function asImpl(Comp) { 39 | return () => ( 40 |
48 | 49 |
50 | ) 51 | } 52 | 53 | export const Standard = asImpl(StandardTabs) 54 | export const Above = asImpl(AboveTabs) 55 | -------------------------------------------------------------------------------- /components/tabs/standard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Tabs} from './base' 3 | import {TabItem, TabItems, TabButtons, TabButton} from '../shared' 4 | 5 | function StandardTabs({items}) { 6 | return ( 7 | 8 | {({openIndexes, handleItemClick}) => ( 9 |
10 | 11 | {items.map((item, index) => ( 12 | handleItemClick(index)} 16 | > 17 | {item.title} 18 | 19 | ))} 20 | 21 | 22 | {items.map((item, index) => ( 23 | 28 | {items[index].contents} 29 | 30 | ))} 31 | 32 |
33 | )} 34 |
35 | ) 36 | } 37 | 38 | export {StandardTabs} 39 | -------------------------------------------------------------------------------- /components/white-layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Slide from 'mdx-deck/dist/Slide' 3 | 4 | export default ({children}) => ( 5 |
17 |
{children}
18 |
19 | ) 20 | -------------------------------------------------------------------------------- /deck.mdx: -------------------------------------------------------------------------------- 1 | import './script.js' 2 | import {Appear, Notes} from 'mdx-deck' 3 | import {FirstSlide} from './components/first-slide' 4 | import {AboutMe} from './components/about-me' 5 | import * as Accordions from './components/accordion/implementations' 6 | import * as Tabs from './components/tabs/implementations' 7 | import {CodeBlock} from './components/code-block' 8 | import WhiteLayout from './components/white-layout' 9 | import {aprocalypse} from './components/aprocalypse' 10 | import {accordionCode} from './components/accordion-code' 11 | import {Sobbing, Sob} from './components/sob' 12 | import {RandomlyPlaced} from './components/randomly-placed' 13 | import {DemoSlide} from './components/demo-slide' 14 | import DemoLayout from './components/demo-layout' 15 | import {Resources} from './components/resources' 16 | 17 | export {default as theme} from './theme' 18 | 19 | export default WhiteLayout 20 | 21 | 25 | 26 | --- 27 | 28 | export default WhiteLayout 29 | 30 | # What this talk is 31 | 32 | - Typical lifecycle of a component
(not what you're 33 | thinking) 34 | - How patterns can simplify 35 | - Composability 36 | - A challenge 37 | 38 | 39 | 40 | --- 41 | 42 | export default WhiteLayout 43 | 44 | # What this talk is _not_ 45 | 46 |
47 |
    48 |
  • 49 | How to implement patterns 50 |
  • 51 |
52 |
53 | 54 | 55 | 56 | --- 57 | 58 | # Let's get started 59 | 60 | 61 | 62 | --- 63 | 64 | export default DemoLayout 65 | 66 | `} 68 | accordion={} 69 | /> 70 | 71 |
72 |
85 |
Theme: Night Owl by Sarah Drasner
86 |
Font: Dank Mono by Phil Plückthun
87 |
88 |
89 |
90 | 91 | 92 | ```notes 93 | - Following the lifecycle of an Accordion 94 | - Make it for your use case 95 | - Props API is pretty simple 96 | - Theme/Font shoutout 97 | - New Use Case: Above 98 | ``` 99 | 100 | --- 101 | 102 | export default DemoLayout 103 | 104 | `} 106 | accordion={} 107 | /> 108 | 109 | ```notes 110 | - Just a few conditional statements (position, hand, animation) 111 | - New Use Case: Right 112 | ``` 113 | 114 | --- 115 | 116 | export default DemoLayout 117 | 118 | `} 120 | accordion={} 121 | /> 122 | 123 | ```notes 124 | - Refactor to use CSS Grid and use gridAutoFlow for column vs row 125 | - New Use Case: Left 126 | ``` 127 | 128 | --- 129 | 130 | export default DemoLayout 131 | 132 | `} 134 | accordion={} 135 | followCode={``} 136 | /> 137 | 138 | ```notes 139 | - Refactor the props to use position because David K. Piano said impossible states is a no-no 140 | - New Use Case: Single 141 | ``` 142 | 143 | --- 144 | 145 | export default DemoLayout 146 | 147 | 154 | `} 155 | accordion={} 156 | /> 157 | 158 | ```notes 159 | - Some conditional logic in the click handler 160 | - New Use Case: Prevent Close 161 | ``` 162 | 163 | --- 164 | 165 | export default DemoLayout 166 | 167 | 174 | `} 175 | accordion={} 176 | /> 177 | 178 | ```notes 179 | - Some additional conditional logic in the click handler 180 | - New Use Case: SinglePreventClose 181 | ``` 182 | 183 | --- 184 | 185 | export default DemoLayout 186 | 187 | 195 | `} 196 | accordion={} 197 | /> 198 | 199 | ```notes 200 | - Luckily no changes necessary! 201 | - But this looks familiar! New Use Case: Tabs 202 | ``` 203 | 204 | --- 205 | 206 | export default DemoLayout 207 | 208 | 217 | `} 218 | accordion={} 219 | /> 220 | 221 | 222 |
223 | 224 | 225 | 226 | ```notes 227 | - Write tabs UI in a different method `renderTabs` 228 | - Cry! 229 | - New Use Case: Tabs Above 230 | ``` 231 | 232 | --- 233 | 234 | export default DemoLayout 235 | 236 | `} 238 | accordion={} 239 | /> 240 | 241 | 242 |
243 | 244 | 245 | 246 | ```notes 247 | - I don't want to maintain this anymore 248 | - Fork the component to make a tabs component 249 | - Sob 250 | ``` 251 | 252 | --- 253 | 254 | {aprocalypse} 255 | 256 | 257 | 258 | ```notes 259 | - Datepicker with 60 props 260 | - "Configuration Props" (objects with dozens of options, basically just more props) 261 | - "render" props: boolean props to control whether something is rendered 262 | - Bundle: Code you don't need 263 | - Maintenance: Documentation, answering questions 264 | - Implementation: Remembering use cases and not breaking things. Contributors. 265 | - API: Having to learn the growing API 266 | - APROPCALYPSE 267 | ``` 268 | 269 | --- 270 | 271 | 272 | 273 | ```notes 274 | (notes for next slide...) 275 | - I might have manually changed the code post-prettier :) 276 | - Don't worry about the specific implementation details. 277 | - Two major patterns that allow this: 278 | - stateReducer 279 | - render props 280 | - Inversion of control patterns 281 | ``` 282 | 283 | --- 284 | 285 | export default ({children}) => ( 286 |
{children}
287 | ) 288 | 289 | {accordionCode} 290 | 291 | 296 | 297 | --- 298 | 299 | # Demo 300 | 301 | [CodeSandbox](https://codesandbox.io/s/simply-react-accordion-lvg59) 302 | 303 | Thank you Ives ❣️ 304 | 305 | ```notes 306 | - Composition allows for the same simple API 307 | ``` 308 | 309 | --- 310 | 311 | 312 | 313 | --- 314 | 315 | # A word of caution 316 | 317 | What if this is all you will ever need? 318 | 319 |
327 |
328 | 329 |
330 |
331 | 332 | --- 333 | 334 | # YAGNI 335 | 336 | You Ain't Gonna Need It 337 | 338 |
339 | Optimize for deletability 340 |
341 |
342 |
351 |
352 | 353 | ```notes 354 | - Be mindful and intentional 355 | - Measure the costs and the benefits. 356 | - Just go with what you have and see how it plays out. 357 | - Optimize for deletability 358 | - Erased from existence! 359 | ``` 360 | 361 | --- 362 | 363 | # Use patterns that _simplify_ your API 364 | 365 | 366 |
367 | 374 | state reducers 375 | 376 |
377 |
378 | 385 | control props 386 | 387 |
388 |
389 | 396 | hook composition 397 | 398 |
399 |
400 | 407 | compound components 408 | 409 |
410 |
411 | 424 | Let's (re)discover more! 425 | 426 |
427 |
428 | 429 | ```notes 430 | - Applying old principles (like composition and inversion of control) 431 | ``` 432 | 433 | --- 434 | 435 | # Thank You 436 | 437 | 438 | 439 | [simply-react.netlify.com](https://simply-react.netlify.com) 440 | 441 | 442 | @kentcdodds 443 | 444 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simply-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "mdx-deck deck.mdx", 8 | "prebuild": "rimraf dist", 9 | "build": "npm run build:site && npm run build:pdf", 10 | "build:site": "mdx-deck build deck.mdx", 11 | "build:pdf": "mdx-deck pdf deck.mdx", 12 | "postbuild": "cpy --parents \"public/**\" ./dist" 13 | }, 14 | "keywords": [], 15 | "author": "Kent C. Dodds (http://kentcdodds.com/)", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "cpy-cli": "^2.0.0", 19 | "css-loader": "^1.0.0", 20 | "mdx-deck": "^1.6.3", 21 | "rimraf": "^2.6.2", 22 | "style-loader": "^0.22.1" 23 | }, 24 | "dependencies": { 25 | "codemirror": "^5.39.2", 26 | "emotion": "^9.2.6", 27 | "react": "^16.13.1", 28 | "react-codemirror2": "^5.1.0", 29 | "react-dom": "^16.13.1", 30 | "react-emotion": "^9.2.6", 31 | "react-pose": "^2.2.1", 32 | "react-transition-group": "^2.4.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/apropcalypse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/apropcalypse.png -------------------------------------------------------------------------------- /public/cost-benefits.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/cost-benefits.jpg -------------------------------------------------------------------------------- /public/cry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/cry.png -------------------------------------------------------------------------------- /public/epic-react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/epic-react.png -------------------------------------------------------------------------------- /public/erased-from-existence.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/erased-from-existence.mp4 -------------------------------------------------------------------------------- /public/sob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/sob.png -------------------------------------------------------------------------------- /public/standard/3-minutes-with-kent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/3-minutes-with-kent.png -------------------------------------------------------------------------------- /public/standard/ama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/ama.png -------------------------------------------------------------------------------- /public/standard/astronaut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/astronaut.png -------------------------------------------------------------------------------- /public/standard/big-smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/big-smile.png -------------------------------------------------------------------------------- /public/standard/books.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/books.png -------------------------------------------------------------------------------- /public/standard/boy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/boy.png -------------------------------------------------------------------------------- /public/standard/dash-race-car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/dash-race-car.png -------------------------------------------------------------------------------- /public/standard/dash-reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/dash-reverse.png -------------------------------------------------------------------------------- /public/standard/dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/dog.png -------------------------------------------------------------------------------- /public/standard/eggheadio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/eggheadio.png -------------------------------------------------------------------------------- /public/standard/fem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/fem.png -------------------------------------------------------------------------------- /public/standard/gde.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/gde.png -------------------------------------------------------------------------------- /public/standard/girl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/girl.png -------------------------------------------------------------------------------- /public/standard/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/github.png -------------------------------------------------------------------------------- /public/standard/house.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/house.png -------------------------------------------------------------------------------- /public/standard/kcd-news.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/kcd-news.png -------------------------------------------------------------------------------- /public/standard/medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/medium.png -------------------------------------------------------------------------------- /public/standard/neutral-face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/neutral-face.png -------------------------------------------------------------------------------- /public/standard/office.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/office.png -------------------------------------------------------------------------------- /public/standard/racing-car-reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/racing-car-reverse.png -------------------------------------------------------------------------------- /public/standard/smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/smile.png -------------------------------------------------------------------------------- /public/standard/speaking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/speaking.png -------------------------------------------------------------------------------- /public/standard/trophy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/trophy.png -------------------------------------------------------------------------------- /public/standard/twitch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/twitch.png -------------------------------------------------------------------------------- /public/standard/twitter.svg: -------------------------------------------------------------------------------- 1 | Twitter_Logo_Blue -------------------------------------------------------------------------------- /public/standard/wave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/wave.png -------------------------------------------------------------------------------- /public/standard/woman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/woman.png -------------------------------------------------------------------------------- /public/standard/workshopme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/workshopme.png -------------------------------------------------------------------------------- /public/standard/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/standard/youtube.png -------------------------------------------------------------------------------- /public/sweat-smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/sweat-smile.png -------------------------------------------------------------------------------- /public/what-if.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/simply-react/82124c414b42c20fe98d22d695188e328e265d61/public/what-if.jpg -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | if (typeof document !== 'undefined') { 2 | require('style-loader!css-loader!codemirror/lib/codemirror.css') 3 | require('style-loader!css-loader!./cm-night-owl.css') 4 | require('style-loader!css-loader!./styles.css') 5 | require('codemirror/mode/javascript/javascript') 6 | require('codemirror/mode/jsx/jsx') 7 | 8 | // make it easier to navigate the slides via the keyboard 9 | document.body.tabIndex = '-1' 10 | window.addEventListener('keyup', e => { 11 | if (e.key === 'Escape') { 12 | document.body.focus() 13 | } 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | .zIEGn { 2 | position: relative; 3 | } 4 | 5 | .react-codemirror2 { 6 | margin-top: 18px; 7 | margin-bottom: 18px; 8 | } 9 | 10 | .react-codemirror2:first-child { 11 | margin-top: 0; 12 | } 13 | 14 | .react-codemirror2:last-child { 15 | margin-bottom: 0; 16 | } 17 | 18 | .CodeMirror { 19 | height: unset; 20 | margin-bottom: 18px; 21 | padding: 14px; 22 | } 23 | 24 | .CodeMirror pre { 25 | font-variant-ligatures: none; 26 | } 27 | 28 | pre { 29 | text-align: left; 30 | } 31 | 32 | a { 33 | color: #addb67; 34 | } 35 | 36 | * { 37 | letter-spacing: unset; 38 | } 39 | -------------------------------------------------------------------------------- /theme.js: -------------------------------------------------------------------------------- 1 | import baseTheme from 'mdx-deck/themes' 2 | 3 | export default { 4 | ...baseTheme, 5 | colors: { 6 | ...baseTheme.colors, 7 | text: '#d6deeb', 8 | background: '#011627', 9 | link: '#addb67', 10 | }, 11 | } 12 | --------------------------------------------------------------------------------