Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 7.0 kB view raw
1import React from 'react'; 2import styled, { css } from 'styled-components'; 3import { MDXProvider } from '@mdx-js/react'; 4import { Link } from 'react-router-dom'; 5import Highlight, { Prism } from 'prism-react-renderer'; 6import nightOwlLight from 'prism-react-renderer/themes/nightOwlLight'; 7 8import AnchorSvg from '../assets/anchor'; 9 10const getLanguage = className => { 11 const res = className.match(/language-(\w+)/); 12 return res ? res[1] : null; 13}; 14 15const Pre = styled.pre` 16 background: ${p => p.theme.colors.codeBg}; 17 border: 1px solid ${p => p.theme.colors.border}; 18 border-radius: ${p => p.theme.spacing.xs}; 19 20 font-size: ${p => p.theme.fontSizes.code}; 21 line-height: ${p => p.theme.lineHeights.code}; 22 23 max-width: 100%; 24 overflow-x: auto; 25 -webkit-overflow-scrolling: touch; 26 padding: ${p => p.theme.spacing.sm}; 27 position: relative; 28 white-space: pre; 29`; 30 31const Code = styled.code` 32 display: block; 33 font-family: ${p => p.theme.fonts.code}; 34 color: ${p => p.theme.colors.code}; 35 font-variant-ligatures: none; 36 font-feature-settings: normal; 37 white-space: pre; 38 hyphens: initial; 39`; 40 41const InlineCode = styled(props => { 42 const children = props.children.replace(/\\\|/g, '|'); 43 return <code {...props}>{children}</code>; 44})` 45 background: ${p => p.theme.colors.codeBg}; 46 color: ${p => p.theme.colors.code}; 47 font-family: ${p => p.theme.fonts.code}; 48 font-size: ${p => p.theme.fontSizes.small}; 49 border-radius: ${p => p.theme.spacing.xs}; 50 51 display: inline-block; 52 vertical-align: baseline; 53 font-variant-ligatures: none; 54 font-feature-settings: normal; 55 padding: 0 0.2em; 56 margin: 0; 57 58 a > & { 59 text-decoration: underline; 60 } 61`; 62 63const InlineImage = styled.img` 64 display: inline-block; 65 margin: 0 ${p => p.theme.spacing.sm} ${p => p.theme.spacing.md} 0; 66 padding: ${p => p.theme.spacing.xs} ${p => p.theme.spacing.sm}; 67 border: 1px solid ${p => p.theme.colors.border}; 68 border-radius: ${p => p.theme.spacing.xs}; 69`; 70 71const ImageWrapper = styled.div` 72 margin: ${p => p.theme.spacing.md} 0; 73 border: 1px solid ${p => p.theme.colors.border}; 74 border-radius: ${p => p.theme.spacing.xs}; 75 background: ${p => p.theme.colors.bg}; 76 77 display: flex; 78 flex-direction: column; 79 80 & > img { 81 padding: ${p => p.theme.spacing.md}; 82 align-self: center; 83 max-height: 40vh; 84 } 85`; 86 87const ImageAlt = styled.span.attrs(() => ({ 88 'aria-hidden': true, // This is just duplicating alt 89}))` 90 display: block; 91 padding: ${p => p.theme.spacing.xs} ${p => p.theme.spacing.sm}; 92 border-top: 1px solid ${p => p.theme.colors.border}; 93 background: ${p => p.theme.colors.codeBg}; 94 font-size: ${p => p.theme.fontSizes.small}; 95`; 96 97const Image = props => { 98 const { height, width, alt, src } = props; 99 if (height || width) return <InlineImage {...props} />; 100 101 return ( 102 <ImageWrapper> 103 <img alt={alt} src={src} /> 104 <ImageAlt>{alt}</ImageAlt> 105 </ImageWrapper> 106 ); 107}; 108 109const HighlightCode = ({ className = '', children }) => { 110 const language = getLanguage(className); 111 112 return ( 113 <Highlight 114 Prism={Prism} 115 theme={nightOwlLight} 116 code={children.trim()} 117 language={language} 118 > 119 {({ className, style, tokens, getLineProps, getTokenProps }) => ( 120 <Code 121 style={{ ...style, backgroundColor: 'none' }} 122 className={className} 123 > 124 {tokens.map((line, i) => ( 125 <div {...getLineProps({ line, key: i })}> 126 {line.map((token, key) => ( 127 <span {...getTokenProps({ token, key })} /> 128 ))} 129 </div> 130 ))} 131 </Code> 132 )} 133 </Highlight> 134 ); 135}; 136 137const Blockquote = styled.blockquote` 138 margin: ${p => p.theme.spacing.md} 0; 139 padding: 0 0 0 ${p => p.theme.spacing.md}; 140 border-left: 0.5rem solid ${p => p.theme.colors.border}; 141 font-size: ${p => p.theme.fontSizes.small}; 142 143 & > * { 144 margin: ${p => p.theme.spacing.sm} 0; 145 } 146`; 147 148const sharedTableCellStyling = css` 149 padding: ${p => p.theme.spacing.xs} ${p => p.theme.spacing.sm}; 150 border-left: 1px solid ${p => p.theme.colors.passiveBg}; 151 border-bottom: 1px solid ${p => p.theme.colors.passiveBg}; 152 153 & > ${InlineCode} { 154 white-space: pre-wrap; 155 display: inline; 156 } 157`; 158 159const TableHeader = styled.th` 160 text-align: left; 161 white-space: nowrap; 162 ${sharedTableCellStyling} 163`; 164 165const TableCell = styled.td` 166 ${sharedTableCellStyling} 167 168 ${p => { 169 const isCodeOnly = React.Children.toArray(p.children).every( 170 x => x.props && x.props.mdxType === 'inlineCode' 171 ); 172 return ( 173 isCodeOnly && 174 css` 175 background-color: ${p.theme.colors.codeBg}; 176 177 && > ${InlineCode} { 178 background: none; 179 padding: 0; 180 margin: 0; 181 white-space: pre; 182 display: block; 183 } 184 ` 185 ); 186 }} 187 188 &:first-child { 189 width: min-content; 190 min-width: 25rem; 191 } 192 193 @media ${p => p.theme.media.md} { 194 &:not(:first-child) { 195 overflow-wrap: break-word; 196 } 197 } 198`; 199 200const TableScrollContainer = styled.div` 201 overflow-x: auto; 202 203 @media ${p => p.theme.media.maxmd} { 204 overflow-x: scroll; 205 -webkit-overflow-scrolling: touch; 206 } 207`; 208 209const Table = styled.table` 210 border: 1px solid ${p => p.theme.colors.passiveBg}; 211 border-collapse: collapse; 212 overflow-x: auto; 213 214 @media ${p => p.theme.media.maxmd} { 215 overflow-x: scroll; 216 overflow-wrap: initial; 217 word-wrap: initial; 218 word-break: initial; 219 hyphens: initial; 220 } 221`; 222 223const TableScroll = props => ( 224 <TableScrollContainer> 225 <Table {...props} /> 226 </TableScrollContainer> 227); 228 229const MdLink = ({ href, children }) => { 230 if (!/^\w+:/.test(href) && !href.startsWith('#')) { 231 return <Link to={href}>{children}</Link>; 232 } 233 234 return ( 235 <a rel="external" href={href}> 236 {children} 237 </a> 238 ); 239}; 240 241const HeadingText = styled.h1` 242 &:target:before { 243 content: ''; 244 display: block; 245 height: 1.5em; 246 margin: -1.5em 0 0; 247 } 248`; 249 250const AnchorLink = styled.a` 251 display: inline-block; 252 color: ${p => p.theme.colors.accent}; 253 padding-right: 0.5rem; 254 width: 2rem; 255 256 @media ${({ theme }) => theme.media.sm} { 257 margin-left: -2rem; 258 display: none; 259 260 ${HeadingText}:hover > & { 261 display: inline-block; 262 } 263 } 264`; 265 266const AnchorIcon = styled(AnchorSvg)` 267 height: 100%; 268`; 269 270const Header = tag => { 271 const HeaderComponent = ({ id, children }) => ( 272 <HeadingText as={tag} id={id}> 273 <AnchorLink href={`#${id}`}> 274 <AnchorIcon /> 275 </AnchorLink> 276 {children} 277 </HeadingText> 278 ); 279 280 HeaderComponent.displayName = `Header(${tag})`; 281 return HeaderComponent; 282}; 283 284const components = { 285 pre: Pre, 286 img: Image, 287 blockquote: Blockquote, 288 inlineCode: InlineCode, 289 code: HighlightCode, 290 table: TableScroll, 291 th: TableHeader, 292 td: TableCell, 293 a: MdLink, 294 h1: HeadingText, 295 h2: Header('h2'), 296 h3: Header('h3'), 297}; 298 299export const MDXComponents = ({ children }) => ( 300 <MDXProvider components={components}>{children}</MDXProvider> 301);