diff --git a/.eslintrc.js b/.eslintrc.js index 2fd1da435ef..a2b42dc5091 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,28 +12,17 @@ module.exports = { 'plugin:import/recommended', 'prettier', ], + settings: { + import: { + ignore: ['^theme$'], + }, + }, rules: { 'react/prop-types': 'off', + // our theme use exports which dont work with import/no-unresolved + 'import/no-unresolved': ['error', {ignore: ['^theme$']}], }, overrides: [ - // { - // files: ['**/src/**/*.js'], - // // env: { - // // commonjs: false, - // // browser: true, - // // }, - // // parserOptions: { - // // sourceType: 'module', - // // }, - // }, - // { - // files: ['**/gatsby-*.js'], - // // env: {node: true}, - // }, - // { - // files: ['**/test/*', '**/__tests__/*'], - // env: {jest: true}, - // }, { files: ['src/shared.js'], rules: { diff --git a/.gitignore b/.gitignore index 4d2cd0e1288..76179b2041b 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ !/CONTRIBUTING.md !/docs/ !/gatsby-*.js +!/jest*.js !/lib/ !/LICENSE* !/map.js @@ -33,7 +34,7 @@ !/release-please-config.json !/scripts/ !/SECURITY.md -!/src/ +!/src !/static/ !/tap-snapshots/ !/test/ diff --git a/content/index.mdx b/content/index.mdx index 746ef7ff540..74e66ee27b0 100644 --- a/content/index.mdx +++ b/content/index.mdx @@ -3,7 +3,7 @@ title: npm Documentation edit_on_github: false --- -import HeroLayout from 'theme/src/layout-hero' +import HeroLayout from 'theme/layout/hero.js' export default HeroLayout diff --git a/theme/jest-preprocess.js b/jest-preprocess.js similarity index 100% rename from theme/jest-preprocess.js rename to jest-preprocess.js diff --git a/theme/jest-setup.js b/jest-setup.js similarity index 100% rename from theme/jest-setup.js rename to jest-setup.js diff --git a/theme/jest.config.js b/jest.config.js similarity index 100% rename from theme/jest.config.js rename to jest.config.js diff --git a/scripts/template-oss/index.js b/scripts/template-oss/index.js index c396a5638da..c22d709269f 100644 --- a/scripts/template-oss/index.js +++ b/scripts/template-oss/index.js @@ -38,12 +38,13 @@ module.exports = { devDependencies: [], }, allowPaths: [ + '/src', '/.reuse/', - '/src/', '/static/', '/content/', '/LICENSE*', '/gatsby-*.js', + '/jest*.js', '/CODE_OF_CONDUCT.md', '/CONTRIBUTING.md', '/CONTENT-MODEL.md', diff --git a/src/shared.js b/src/shared.js index 65ee2c8e0cf..0f94370d755 100644 --- a/src/shared.js +++ b/src/shared.js @@ -1,7 +1,5 @@ import React from 'react' -import {Link} from '@primer/react' -import Screenshot from 'theme/src/mdx/screenshot' -import Note from 'theme/src/mdx/note' +import {Link, Note, Screenshot} from 'theme' const shared = { /* User login */ diff --git a/theme/.gitignore b/theme/.gitignore index 0cb3fab190d..fcff975a1b0 100644 --- a/theme/.gitignore +++ b/theme/.gitignore @@ -12,7 +12,6 @@ !/CHANGELOG* !/docs/ !/gatsby-*.js -!/jest*.js !/lib/ !/LICENSE* !/map.js diff --git a/theme/gatsby-config.js b/theme/gatsby-config.js index ea5d97c15c2..03c891f85e4 100644 --- a/theme/gatsby-config.js +++ b/theme/gatsby-config.js @@ -53,7 +53,7 @@ module.exports = ({icon}) => ({ options: { extensions: ['.mdx', '.md'], defaultLayouts: { - default: require.resolve('./src/layout-default.js'), + default: require.resolve('./src/layout/default.js'), }, }, }, diff --git a/theme/package.json b/theme/package.json index c5d05f56e75..108d5bad3a9 100644 --- a/theme/package.json +++ b/theme/package.json @@ -7,7 +7,10 @@ "type": "git" }, "private": true, - "main": "index.js", + "exports": { + ".": "./src/mdx/index.js", + "./layout/*.js": "./src/layout/*.js" + }, "license": "MIT", "scripts": { "test": "jest", diff --git a/theme/scripts/template-oss/index.js b/theme/scripts/template-oss/index.js index cfd63a1727a..cbbbcc00e13 100644 --- a/theme/scripts/template-oss/index.js +++ b/theme/scripts/template-oss/index.js @@ -1,4 +1,4 @@ module.exports = { ...require('../../../scripts/template-oss'), - allowPaths: ['/src', '/gatsby-*.js', '/jest*.js'], + allowPaths: ['/src', '/gatsby-*.js'], } diff --git a/theme/src/components/contributors.js b/theme/src/components/contributors.js index 7c190cf2e5f..93e243eef0b 100644 --- a/theme/src/components/contributors.js +++ b/theme/src/components/contributors.js @@ -1,6 +1,5 @@ -import {Avatar, Link, Text, Tooltip} from '@primer/react' +import {Box, Avatar, Link, Text, Tooltip} from '@primer/react' import React from 'react' -import Flex from '../components/flex' const pluralize = (word, count) => `${word}${count === 1 ? '' : 's'}` @@ -23,7 +22,7 @@ const format = d => `${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}` function Contributors({logins, latestCommit}) { return (
- + {logins.length} {pluralize('contributor', logins.length)} @@ -35,7 +34,7 @@ function Contributors({logins, latestCommit}) { ))} - + {latestCommit ? ( Last edited by {latestCommit.login} on{' '} diff --git a/theme/src/components/flex.js b/theme/src/components/flex.js deleted file mode 100644 index 810efd827e5..00000000000 --- a/theme/src/components/flex.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import {Box} from '@primer/react' - -const Flex = props => - -export default Flex diff --git a/theme/src/components/header.js b/theme/src/components/header.js index d6f96876665..82032bebae1 100644 --- a/theme/src/components/header.js +++ b/theme/src/components/header.js @@ -6,7 +6,6 @@ import MobileSearch from './mobile-search' import NavDrawer from './nav-drawer' import Search from './search' import NpmLogo from './npm-logo' -import Flex from './flex' import useSearch from '../hooks/use-search' import useSiteMetadata from '../hooks/use-site-metadata' import headerNavItems from '../header-nav.yml' @@ -33,14 +32,15 @@ function Header({location, repositoryUrl}) { return ( - - + {siteMetadata.title} @@ -48,30 +48,30 @@ function Header({location, repositoryUrl}) { - - + + - + - - - + + + ) } function HeaderNavItems({items}) { return ( - + {items.map((item, index) => ( {item.title} ))} - + ) } diff --git a/theme/src/components/mobile-search.js b/theme/src/components/mobile-search.js index 420f0521f7c..2252f1fc157 100644 --- a/theme/src/components/mobile-search.js +++ b/theme/src/components/mobile-search.js @@ -3,7 +3,6 @@ import {Box} from '@primer/react' import {XIcon, SearchIcon} from '@primer/octicons-react' import {AnimatePresence, motion} from 'framer-motion' import {FocusOn} from 'react-focus-on' -import Flex from './flex' import DarkButton from './dark-button' import DarkTextInput from './dark-text-input' import SearchResults from './search-results' @@ -35,8 +34,8 @@ function MobileSearch({onDismiss, ...props}) { zIndex={-1} onClick={handleDismiss} /> - - + + - - + {isOpen ? : null} - - + + ) diff --git a/theme/src/components/nav-drawer.js b/theme/src/components/nav-drawer.js index 715361a295b..0dac98cfb28 100644 --- a/theme/src/components/nav-drawer.js +++ b/theme/src/components/nav-drawer.js @@ -1,12 +1,11 @@ import React from 'react' -import {Link} from '@primer/react' +import {Box, Link} from '@primer/react' import BorderBox from './border-box' import {XIcon, ThreeBarsIcon} from '@primer/octicons-react' import {Link as GatsbyLink} from 'gatsby' import DarkButton from './dark-button' import Drawer from './drawer' import NavItems from './nav-items' -import Flex from './flex' import navItems from '../nav.yml' import headerNavItems from '../header-nav.yml' import useSiteMetadata from '../hooks/use-site-metadata' @@ -37,35 +36,45 @@ function NavDrawer({location, repositoryUrl}) { - - + - + {siteMetadata.title} - + {navItems.length > 0 ? ( - + - + ) : null} - + {headerNavItems.length > 0 ? ( - + - + ) : null} - + ) diff --git a/theme/src/components/nav-items.js b/theme/src/components/nav-items.js index 29fd77a35b9..6d4a9c12b84 100644 --- a/theme/src/components/nav-items.js +++ b/theme/src/components/nav-items.js @@ -4,7 +4,6 @@ import {Box, StyledOcticon, Link, themeGet} from '@primer/react' import {LinkExternalIcon} from '@primer/octicons-react' import styled from 'styled-components' import BorderBox from './border-box' -import Flex from './flex' import NavHierarchy from '../util/nav-hierarchy' const getActiveProps = className => props => { @@ -90,12 +89,12 @@ function topLevelItems(items, path) { return ( - + {item.title} {secondLevelItems(children, path)} - + ) })} @@ -109,7 +108,7 @@ function secondLevelItems(items, path) { } return ( - + {items.map(item => { const children = NavHierarchy.isActiveUrl(path, item.url) ? NavHierarchy.getHierarchy(item, {path, hideVariants: true}) @@ -128,7 +127,7 @@ function secondLevelItems(items, path) { ) })} - + ) } @@ -138,7 +137,7 @@ function thirdLevelItems(items) { } return ( - + {items.map(item => ( @@ -146,7 +145,7 @@ function thirdLevelItems(items) { ))} - + ) } @@ -159,10 +158,10 @@ function NavItems({location, repositoryUrl}) { {topLevelItems(items, path)} - + GitHub - + diff --git a/theme/src/components/search-results.js b/theme/src/components/search-results.js index 326e916fbed..901621f34c2 100644 --- a/theme/src/components/search-results.js +++ b/theme/src/components/search-results.js @@ -1,6 +1,5 @@ import React from 'react' -import {Text} from '@primer/react' -import Flex from './flex' +import {Box, Text} from '@primer/react' import useSiteMetadata from '../hooks/use-site-metadata' import NavHierarchy from '../util/nav-hierarchy' @@ -16,7 +15,8 @@ function SearchResults({results, getItemProps, highlightedIndex}) { } return results.map((item, index) => ( - {item.title} - + )) } diff --git a/theme/src/components/sidebar.js b/theme/src/components/sidebar.js index 85901eaf20f..041543e1cc8 100644 --- a/theme/src/components/sidebar.js +++ b/theme/src/components/sidebar.js @@ -1,6 +1,5 @@ import {Box} from '@primer/react' import React from 'react' -import Flex from './flex' import {HEADER_HEIGHT} from './header' import NavItems from './nav-items' import BorderBox from './border-box' @@ -18,9 +17,9 @@ function Sidebar({location, repositoryUrl}) { role="navigation" > - + - + ) diff --git a/theme/src/components/variant-select.js b/theme/src/components/variant-select.js index 9f305113449..2e58945980e 100644 --- a/theme/src/components/variant-select.js +++ b/theme/src/components/variant-select.js @@ -8,41 +8,27 @@ import NavHierarchy from '../util/nav-hierarchy' // second folder acts as a variant. If you use // then you'll get a selection for the different variants (v1.0, v2.0). -function VariantSelect(props) { +const VariantSelect = ({variantPages, path}) => { const [open, setOpen] = React.useState(false) - const path = NavHierarchy.getPath(props.location.pathname) - const vp = NavHierarchy.getVariantAndPage(props.root, path) - - if (!vp) { - return null - } - - const variantPages = NavHierarchy.getVariantsForPage(props.root, vp.page) - const items = [] - let selectedItem = variantPages[0] - - if (variantPages.length === 0) { - return null - } - - function anchorClickHandler(event, url) { + const anchorClickHandler = React.useCallback((event, url) => { event.preventDefault() window.location.href = `${url}?v=true` - } + }, []) - function onItemEnterKey(event, url) { + const onItemEnterKey = React.useCallback((event, url) => { if (event.key === 'Enter') { window.location.href = `${url}?v=true` } - } + }, []) - for (const [index, match] of variantPages.entries()) { + let selectedItem = variantPages[0] + const items = variantPages.map((match, index) => { let active = false if (match.page.url === path) { selectedItem = match active = true } - items.push( + return ( onItemEnterKey(e, match.page.url)} onClick={e => anchorClickHandler(e, match.page.url)} @@ -51,9 +37,9 @@ function VariantSelect(props) { active={active} > {match.variant.title} - , + ) - } + }) return ( <> @@ -75,4 +61,16 @@ function VariantSelect(props) { ) } -export default VariantSelect +const VariantSelectLocation = ({root, location}) => { + const path = NavHierarchy.getPath(location.pathname) + const vp = NavHierarchy.getVariantAndPage(root, path) + const variantPages = vp ? NavHierarchy.getVariantsForPage(root, vp.page) : [] + + if (!variantPages.length) { + return null + } + + return +} + +export default VariantSelectLocation diff --git a/theme/src/hooks/use-scroll-size.js b/theme/src/hooks/use-scroll-size.js new file mode 100644 index 00000000000..1c232ed1754 --- /dev/null +++ b/theme/src/hooks/use-scroll-size.js @@ -0,0 +1,37 @@ +import {createRef, useState, useEffect} from 'react' + +/** + * Resize the scroll handle to the size of the code contents, since the former has to be positioned absolutely. + */ +const useScrollSize = () => { + const scrollRef = createRef() + const paddingRef = createRef() + const [size, setSize] = useState({}) + + useEffect(() => { + const scrollNode = scrollRef.current + const paddingNode = paddingRef.current + + if (!scrollNode || !paddingNode || typeof size.width !== 'undefined') { + return + } + + const parent = scrollNode.parentElement + const button = paddingNode.firstChild + + parent.style.position = 'relative' + const parentStyle = getComputedStyle(parent) + const paddingTop = parseInt(parentStyle.paddingTop, 10) + const paddingBottom = parseInt(parentStyle.paddingBottom, 10) + const paddingRight = parseInt(parentStyle.paddingRight, 10) + + setSize({ + height: parent.clientHeight - paddingTop - paddingBottom, + width: parent.scrollWidth - paddingRight + button.clientWidth, + }) + }, [scrollRef, paddingRef, size]) + + return {scrollRef, paddingRef, size} +} + +export default useScrollSize diff --git a/theme/src/layout-hero.js b/theme/src/layout-hero.js deleted file mode 100644 index ada637e4ba2..00000000000 --- a/theme/src/layout-hero.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react' -import {Box} from '@primer/react' -import Container from './components/container' -import Head from './components/head' -import Header from './components/header' -import Hero from './components/hero' -import Sidebar from './components/sidebar' -import Flex from './components/flex' -import * as Slugger from './hooks/use-slugger' - -function HeroLayout({children, pageContext, location}) { - return ( - - - -
- - - - - - - {children} - - - - - ) -} - -export default HeroLayout diff --git a/theme/src/layout-default.js b/theme/src/layout/default.js similarity index 72% rename from theme/src/layout-default.js rename to theme/src/layout/default.js index 400fa264d50..d1a96cb34aa 100644 --- a/theme/src/layout-default.js +++ b/theme/src/layout/default.js @@ -1,30 +1,30 @@ import React from 'react' import {Box, Heading, StyledOcticon, Text, Details} from '@primer/react' import {ChevronDownIcon, ChevronRightIcon} from '@primer/octicons-react' -import Head from './components/head' -import Header, {HEADER_HEIGHT} from './components/header' -import PageFooter from './components/page-footer' -import Sidebar from './components/sidebar' -import TableOfContents from './components/table-of-contents' -import VariantSelect from './components/variant-select' -import BorderBox from './components/border-box' -import Flex from './components/flex' -import * as Slugger from './hooks/use-slugger' -import NavHierarchy from './util/nav-hierarchy' +import Head from '../components/head' +import Header, {HEADER_HEIGHT} from '../components/header' +import PageFooter from '../components/page-footer' +import Sidebar from '../components/sidebar' +import TableOfContents from '../components/table-of-contents' +import VariantSelect from '../components/variant-select' +import BorderBox from '../components/border-box' +import * as Slugger from '../hooks/use-slugger' +import NavHierarchy from '../util/nav-hierarchy' function Layout({children, pageContext, location}) { - const {title, description} = pageContext.frontmatter + const {repositoryUrl, editUrl, contributors, frontmatter, tableOfContents} = pageContext + const {title, description} = frontmatter const variantRoot = NavHierarchy.getVariantRoot(location.pathname) return ( - + -
- +
+ - + ) : null} - {pageContext.tableOfContents ? ( + {tableOfContents ? ( Table of contents - + ) : null} - {pageContext.tableOfContents ? ( + {tableOfContents ? (
{({open}) => ( @@ -93,7 +93,7 @@ function Layout({children, pageContext, location}) { Table of contents - + )} @@ -101,11 +101,11 @@ function Layout({children, pageContext, location}) { ) : null} {children} - + - - + + ) } diff --git a/theme/src/layout/hero.js b/theme/src/layout/hero.js new file mode 100644 index 00000000000..f5e99dd700b --- /dev/null +++ b/theme/src/layout/hero.js @@ -0,0 +1,28 @@ +import React from 'react' +import {Box} from '@primer/react' +import Container from '../components/container' +import Head from '../components/head' +import Header from '../components/header' +import Hero from '../components/hero' +import Sidebar from '../components/sidebar' +import * as Slugger from '../hooks/use-slugger' + +const HeroLayout = ({children, location, pageContext: {repositoryUrl}}) => ( + + + +
+ + + + + + + {children} + + + + +) + +export default HeroLayout diff --git a/theme/src/mdx/blockquote.js b/theme/src/mdx/blockquote.js deleted file mode 100644 index 11c6f32768d..00000000000 --- a/theme/src/mdx/blockquote.js +++ /dev/null @@ -1,19 +0,0 @@ -import styled from 'styled-components' -import {themeGet} from '@primer/react' - -const Blockquote = styled.blockquote` - margin: 0 0 ${themeGet('space.3')}; - padding: 0 ${themeGet('space.3')}; - color: ${themeGet('colors.gray.5')}; - border-left: 0.25em solid ${themeGet('colors.gray.2')}; - - > :first-child { - margin-top: 0; - } - - > :last-child { - margin-bottom: 0; - } -` - -export default Blockquote diff --git a/theme/src/mdx/code.js b/theme/src/mdx/code.js index 303ab5926cc..5978e12b65d 100644 --- a/theme/src/mdx/code.js +++ b/theme/src/mdx/code.js @@ -1,46 +1,11 @@ +import React from 'react' import {Box, Text} from '@primer/react' import Highlight, {defaultProps} from 'prism-react-renderer' import githubTheme from 'prism-react-renderer/themes/github' -import React, {useState, useEffect} from 'react' import ClipboardCopy from '../components/clipboard-copy' -import BorderBox from '../components/border-box' +import useScrollSize from '../hooks/use-scroll-size' -/** - * Resize the scroll handle to the size of the code contents, since the former has to be positioned absolutely. - */ -const useScrollSize = () => { - const scrollRef = React.createRef() - const paddingRef = React.createRef() - const [size, setSize] = useState({}) - - useEffect(() => { - const scrollNode = scrollRef.current - const paddingNode = paddingRef.current - - if (!scrollNode || !paddingNode || typeof size.width !== 'undefined') { - return - } - - const parent = scrollNode.parentElement - const button = paddingNode.firstChild - - parent.style.position = 'relative' - const parentStyle = getComputedStyle(parent) - const paddingTop = parseInt(parentStyle.paddingTop, 10) - const paddingBottom = parseInt(parentStyle.paddingBottom, 10) - const paddingRight = parseInt(parentStyle.paddingRight, 10) - - setSize({ - height: parent.clientHeight - paddingTop - paddingBottom, - width: parent.scrollWidth - paddingRight + button.clientWidth, - }) - }, [scrollRef, paddingRef, size]) - - return {scrollRef, paddingRef, size} -} - -function Code({className: parentClass, children}) { - const language = parentClass ? parentClass.replace(/language-/, '') : '' +function Code({className: language = '', children}) { const code = children.trim() const {scrollRef, paddingRef, size} = useScrollSize() @@ -51,9 +16,22 @@ function Code({className: parentClass, children}) {
- + {({className, style, tokens, getLineProps, getTokenProps}) => ( - + {/* This is the scroll handle, it is supposed to be focused with keyboard and scroll a wide codebox horizontally */} ))} - + )} diff --git a/theme/src/mdx/description-list.js b/theme/src/mdx/description-list.js deleted file mode 100644 index 6709271dbf6..00000000000 --- a/theme/src/mdx/description-list.js +++ /dev/null @@ -1,21 +0,0 @@ -import styled from 'styled-components' -import {themeGet} from '@primer/react' - -const DescriptionList = styled.dl` - padding: 0; - - dt { - padding: 0; - margin-top: ${themeGet('space.3')}; - font-size: 1em; - font-style: italic; - font-weight: ${themeGet('fontWeights.bold')}; - } - - dd { - padding: 0 ${themeGet('space.3')}; - margin: 0 0 ${themeGet('space.3')}; - } -` - -export default DescriptionList diff --git a/theme/src/mdx/heading.js b/theme/src/mdx/heading.js deleted file mode 100644 index e27a5426f70..00000000000 --- a/theme/src/mdx/heading.js +++ /dev/null @@ -1,73 +0,0 @@ -import {Heading, Link, themeGet} from '@primer/react' -import {LinkIcon} from '@primer/octicons-react' -import React from 'react' -import textContent from 'react-addons-text-content' -import styled from 'styled-components' -import {HEADER_HEIGHT} from '../components/header' -import {useSlugger} from '../hooks/use-slugger' - -const StyledHeading = styled(Heading)` - margin-top: ${themeGet('space.4')}; - margin-bottom: ${themeGet('space.3')}; - scroll-margin-top: ${HEADER_HEIGHT + 24}px; - - & .octicon-link { - visibility: hidden; - } - - &:hover .octicon-link, - &:focus-within .octicon-link { - visibility: visible; - } -` - -function MarkdownHeading({children, ...props}) { - const slugger = useSlugger() - const text = children ? textContent(children) : '' - const id = text ? slugger.slug(text) : '' - - return ( - - - - - {children} - - ) -} - -const StyledH1 = styled(StyledHeading).attrs({as: 'h1'})` - padding-bottom: ${themeGet('space.1')}; - font-size: ${themeGet('fontSizes.5')}; - border-bottom: 1px solid ${themeGet('colors.gray.2')}; -` - -const StyledH2 = styled(StyledHeading).attrs({as: 'h2'})` - padding-bottom: ${themeGet('space.1')}; - font-size: ${themeGet('fontSizes.4')}; - border-bottom: 1px solid ${themeGet('colors.gray.2')}; -` - -const StyledH3 = styled(StyledHeading).attrs({as: 'h3'})` - font-size: ${themeGet('fontSizes.3')}; -` - -const StyledH4 = styled(StyledHeading).attrs({as: 'h4'})` - font-size: ${themeGet('fontSizes.2')}; -` - -const StyledH5 = styled(StyledHeading).attrs({as: 'h5'})` - font-size: ${themeGet('fontSizes.1')}; -` - -const StyledH6 = styled(StyledHeading).attrs({as: 'h6'})` - font-size: ${themeGet('fontSizes.1')}; - color: ${themeGet('colors.gray.5')}; -` - -export const H1 = props => -export const H2 = props => -export const H3 = props => -export const H4 = props => -export const H5 = props => -export const H6 = props => diff --git a/theme/src/mdx/horizontal-rule.js b/theme/src/mdx/horizontal-rule.js deleted file mode 100644 index 58892f12898..00000000000 --- a/theme/src/mdx/horizontal-rule.js +++ /dev/null @@ -1,12 +0,0 @@ -import styled from 'styled-components' -import {themeGet} from '@primer/react' - -const HorizontalRule = styled.hr` - height: ${themeGet('space.1')}; - padding: 0; - margin: ${themeGet('space.4')} 0; - background-color: ${themeGet('colors.gray.2')}; - border: 0; -` - -export default HorizontalRule diff --git a/theme/src/mdx/image.js b/theme/src/mdx/image.js deleted file mode 100644 index 54f9948946c..00000000000 --- a/theme/src/mdx/image.js +++ /dev/null @@ -1,10 +0,0 @@ -import {themeGet} from '@primer/react' -import styled from 'styled-components' - -const Image = styled.img` - max-width: 100%; - box-sizing: content-box; - background-color: ${themeGet('colors.white')}; -` - -export default Image diff --git a/theme/src/mdx/index.js b/theme/src/mdx/index.js index a012796a2ea..eaf98a08908 100644 --- a/theme/src/mdx/index.js +++ b/theme/src/mdx/index.js @@ -1,50 +1,286 @@ import React from 'react' -import {useLocation} from '@reach/router' // eslint-disable-line import/no-unresolved -import {Box, Link} from '@primer/react' -import {Link as GatsbyLink} from 'gatsby' -import NavHierarchy from '../util/nav-hierarchy' - -function showHierarchy(items, props, depth = 1) { - let hierarchy - - if (props.depth && depth > props.depth) { - return null - } - - return ( - - {items.map(item => ( - - - {item.title} - - {item.description != null ? ( - <> - {item.description} - - ) : null} - {(hierarchy = NavHierarchy.getHierarchy(item, props)) != null - ? showHierarchy(hierarchy, props, depth + 1) - : null} - - ))} - - ) +import {Box, Heading, themeGet, Text, Link as PrimerLink} from '@primer/react' +import styled from 'styled-components' +import {withPrefix} from 'gatsby' +import {LinkIcon} from '@primer/octicons-react' +import textContent from 'react-addons-text-content' +import Code from './code' +import NavHierarchy from './nav-hierarchy' +import {HEADER_HEIGHT} from '../components/header' +import {useSlugger} from '../hooks/use-slugger' + +const required = (prop, name) => { + if (!prop) { + throw new Error(`${name} prop is required`) + } + return prop +} + +export const Link = props => { + return } -function Index(props) { - const location = useLocation() - const path = NavHierarchy.getLocation(location.pathname) - const root = (props.root ? props.root : path).replace(/\/+$/g, '') +export {Code, NavHierarchy as Index} + +export const Pre = ({children}) => children + +const SkipLinkBase = props => ( + + Skip to content + +) + +export const SkipLink = styled(SkipLinkBase)` + z-index: 20; + width: auto; + height: auto; + clip: auto; + position: absolute; + overflow: hidden; - const rootItem = NavHierarchy.getItem(root) - const hierarchy = NavHierarchy.getHierarchy(rootItem, props) + // The following rules are to ensure that the element + // is visually hidden, unless it has focus. This is the recommended + // way to hide content from: + // https://webaim.org/techniques/css/invisiblecontent/#techniques - if (!hierarchy) { - throw new Error(`could not find entry for ${root}`) + &:not(:focus) { + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; } +` + +const StyledHeading = styled(Heading)` + margin-top: ${themeGet('space.4')}; + margin-bottom: ${themeGet('space.3')}; + scroll-margin-top: ${HEADER_HEIGHT + 24}px; + + & .octicon-link { + visibility: hidden; + } + + &:hover .octicon-link, + &:focus-within .octicon-link { + visibility: visible; + } +` + +const Headings = { + Markdown: ({children, ...props}) => { + const slugger = useSlugger() + const text = children ? textContent(children) : '' + const id = text ? slugger.slug(text) : '' - return showHierarchy(hierarchy, props) + return ( + + + + + {children} + + ) + }, + h1: styled(StyledHeading).attrs({as: 'h1'})` + padding-bottom: ${themeGet('space.1')}; + font-size: ${themeGet('fontSizes.5')}; + border-bottom: 1px solid ${themeGet('colors.gray.2')}; + `, + h2: styled(StyledHeading).attrs({as: 'h2'})` + padding-bottom: ${themeGet('space.1')}; + font-size: ${themeGet('fontSizes.4')}; + border-bottom: 1px solid ${themeGet('colors.gray.2')}; + `, + h3: styled(StyledHeading).attrs({as: 'h3'})` + font-size: ${themeGet('fontSizes.3')}; + `, + h4: styled(StyledHeading).attrs({as: 'h4'})` + font-size: ${themeGet('fontSizes.2')}; + `, + h5: styled(StyledHeading).attrs({as: 'h5'})` + font-size: ${themeGet('fontSizes.1')}; + `, + h6: styled(StyledHeading).attrs({as: 'h6'})` + font-size: ${themeGet('fontSizes.1')}; + color: ${themeGet('colors.gray.5')}; + `, + wrap(as) { + return props => + }, } -export default Index +export const H1 = Headings.wrap('h1') +export const H2 = Headings.wrap('h2') +export const H3 = Headings.wrap('h3') +export const H4 = Headings.wrap('h4') +export const H5 = Headings.wrap('h5') +export const H6 = Headings.wrap('h6') + +export const Blockquote = styled.blockquote` + margin: 0 0 ${themeGet('space.3')}; + padding: 0 ${themeGet('space.3')}; + color: ${themeGet('colors.gray.5')}; + border-left: 0.25em solid ${themeGet('colors.gray.2')}; + + > :first-child { + margin-top: 0; + } + + > :last-child { + margin-bottom: 0; + } +` + +export const DescriptionList = styled.dl` + padding: 0; + + dt { + padding: 0; + margin-top: ${themeGet('space.3')}; + font-size: 1em; + font-style: italic; + font-weight: ${themeGet('fontWeights.bold')}; + } + + dd { + padding: 0 ${themeGet('space.3')}; + margin: 0 0 ${themeGet('space.3')}; + } +` + +export const HorizontalRule = styled.hr` + height: ${themeGet('space.1')}; + padding: 0; + margin: ${themeGet('space.4')} 0; + background-color: ${themeGet('colors.gray.2')}; + border: 0; +` + +export const Image = styled.img` + max-width: 100%; + box-sizing: content-box; + background-color: ${themeGet('colors.white')}; +` + +export const InlineCode = styled.code` + padding: 0.2em 0.4em; + font-family: ${themeGet('fonts.mono')}; + font-size: 85%; + background-color: ${themeGet('colors.gray.1')}; + border-radius: ${themeGet('radii.1')}; +` + +export const UnorderedList = styled.ul` + padding-left: 2em; + + ul, + ol { + margin-top: 0; + margin-bottom: 0; + } + + li { + word-wrap: break-all; + } + + li > p { + margin-top: ${themeGet('space.3')}; + } + + li + li { + margin-top: ${themeGet('space.1')}; + } +` + +export const OrderedList = UnorderedList.withComponent('ol') + +export const Paragraph = styled.p` + margin: 0 0 ${themeGet('space.3')}; +` + +export const Table = styled.table` + display: block; + width: 100%; + margin: 0 0 ${themeGet('space.3')}; + overflow: auto; + + th { + font-weight: ${themeGet('fontWeights.bold')}; + } + + th, + td { + padding: ${themeGet('space.2')} ${themeGet('space.3')}; + border: 1px solid ${themeGet('colors.gray.2')}; + } + + tr { + background-color: ${themeGet('colors.white')}; + border-top: 1px solid ${themeGet('colors.gray.2')}; + + &:nth-child(2n) { + background-color: ${themeGet('colors.gray.1')}; + } + } + + img { + background-color: transparent; + } +` + +export const Note = ({children}) => ( + + {React.Children.toArray(children).map((child, index, list) => + React.cloneElement(child, { + style: index === list.length - 1 ? {marginBottom: '0'} : null, + }), + )} + +) + +export const Prompt = ({children}) => ( + + + {children} + + +) + +export const PromptReply = ({children}) => {children} + +export const Screenshot = props => ( +
+ {required(props.alt, +
+) diff --git a/theme/src/mdx/inline-code.js b/theme/src/mdx/inline-code.js deleted file mode 100644 index 02690e2e759..00000000000 --- a/theme/src/mdx/inline-code.js +++ /dev/null @@ -1,12 +0,0 @@ -import styled from 'styled-components' -import {themeGet} from '@primer/react' - -const InlineCode = styled.code` - padding: 0.2em 0.4em; - font-family: ${themeGet('fonts.mono')}; - font-size: 85%; - background-color: ${themeGet('colors.gray.1')}; - border-radius: ${themeGet('radii.1')}; -` - -export default InlineCode diff --git a/theme/src/mdx/list.js b/theme/src/mdx/list.js deleted file mode 100644 index 20cc2c0e0da..00000000000 --- a/theme/src/mdx/list.js +++ /dev/null @@ -1,26 +0,0 @@ -import styled from 'styled-components' -import {themeGet} from '@primer/react' - -const List = styled.ul` - padding-left: 2em; - - ul, - ol { - margin-top: 0; - margin-bottom: 0; - } - - li { - word-wrap: break-all; - } - - li > p { - margin-top: ${themeGet('space.3')}; - } - - li + li { - margin-top: ${themeGet('space.1')}; - } -` - -export default List diff --git a/theme/src/mdx/nav-hierarchy.js b/theme/src/mdx/nav-hierarchy.js new file mode 100644 index 00000000000..a21c06d7816 --- /dev/null +++ b/theme/src/mdx/nav-hierarchy.js @@ -0,0 +1,48 @@ +import React from 'react' +import {useLocation} from '@reach/router' // eslint-disable-line import/no-unresolved +import {Box, Link} from '@primer/react' +import {Link as GatsbyLink} from 'gatsby' +import NavHierarchy from '../util/nav-hierarchy' + +function showHierarchy(items, props, depth = 1) { + let hierarchy + + if (props.depth && depth > props.depth) { + return null + } + + return ( + + {items.map(item => ( + + + {item.title} + + {item.description != null ? ( + {item.description} + ) : null} + {(hierarchy = NavHierarchy.getHierarchy(item, props)) != null + ? showHierarchy(hierarchy, props, depth + 1) + : null} + + ))} + + ) +} + +function Index(props) { + const location = useLocation() + const path = NavHierarchy.getLocation(location.pathname) + const root = (props.root ? props.root : path).replace(/\/+$/g, '') + + const rootItem = NavHierarchy.getItem(root) + const hierarchy = NavHierarchy.getHierarchy(rootItem, props) + + if (!hierarchy) { + throw new Error(`could not find entry for ${root}`) + } + + return showHierarchy(hierarchy, props) +} + +export default Index diff --git a/theme/src/mdx/note.js b/theme/src/mdx/note.js deleted file mode 100644 index e260be3495a..00000000000 --- a/theme/src/mdx/note.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import BorderBox from '../components/border-box' - -function Note({children}) { - return ( - - {React.Children.toArray(children).map((child, index, list) => - React.cloneElement(child, { - style: index === list.length - 1 ? {marginBottom: '0'} : null, - }), - )} - - ) -} - -export default Note diff --git a/theme/src/mdx/paragraph.js b/theme/src/mdx/paragraph.js deleted file mode 100644 index d561ed2c398..00000000000 --- a/theme/src/mdx/paragraph.js +++ /dev/null @@ -1,8 +0,0 @@ -import styled from 'styled-components' -import {themeGet} from '@primer/react' - -const Paragraph = styled.p` - margin: 0 0 ${themeGet('space.3')}; -` - -export default Paragraph diff --git a/theme/src/mdx/prompt-reply.js b/theme/src/mdx/prompt-reply.js deleted file mode 100644 index 732caa0368d..00000000000 --- a/theme/src/mdx/prompt-reply.js +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react' - -function PromptReply({children}) { - return {children} -} - -export default PromptReply diff --git a/theme/src/mdx/prompt.js b/theme/src/mdx/prompt.js deleted file mode 100644 index efcb5247a50..00000000000 --- a/theme/src/mdx/prompt.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react' -import {Text} from '@primer/react' -import BorderBox from '../components/border-box' - -function Prompt({children}) { - return ( - - - {children} - - - ) -} - -export default Prompt diff --git a/theme/src/mdx/screenshot.js b/theme/src/mdx/screenshot.js deleted file mode 100644 index 24ceda8f6cc..00000000000 --- a/theme/src/mdx/screenshot.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import {withPrefix} from 'gatsby' - -function Screenshot(props) { - if (!props.src) { - throw new Error('src is required') - } - - if (!props.alt) { - throw new Error('alt text is required') - } - - return ( -
- {props.alt} -
- ) -} - -export default Screenshot diff --git a/theme/src/mdx/table.js b/theme/src/mdx/table.js deleted file mode 100644 index 99c4dfb68d4..00000000000 --- a/theme/src/mdx/table.js +++ /dev/null @@ -1,34 +0,0 @@ -import styled from 'styled-components' -import {themeGet} from '@primer/react' - -const Table = styled.table` - display: block; - width: 100%; - margin: 0 0 ${themeGet('space.3')}; - overflow: auto; - - th { - font-weight: ${themeGet('fontWeights.bold')}; - } - - th, - td { - padding: ${themeGet('space.2')} ${themeGet('space.3')}; - border: 1px solid ${themeGet('colors.gray.2')}; - } - - tr { - background-color: ${themeGet('colors.white')}; - border-top: 1px solid ${themeGet('colors.gray.2')}; - - &:nth-child(2n) { - background-color: ${themeGet('colors.gray.1')}; - } - } - - img { - background-color: transparent; - } -` - -export default Table diff --git a/theme/src/page-element.js b/theme/src/page-element.js index 35f063765af..358952ddec0 100644 --- a/theme/src/page-element.js +++ b/theme/src/page-element.js @@ -1,6 +1,7 @@ -import {BaseStyles, Link} from '@primer/react' import React from 'react' -import styled, {createGlobalStyle} from 'styled-components' +import {BaseStyles} from '@primer/react' +import {createGlobalStyle} from 'styled-components' +import {SkipLink} from './mdx' const GlobalStyle = createGlobalStyle` ::placeholder { @@ -8,43 +9,12 @@ const GlobalStyle = createGlobalStyle` } ` -const SkipLinkBase = props => ( - - Skip to content - +const PageElement = ({element}) => ( + + + + {element} + ) -const SkipLink = styled(SkipLinkBase)` - z-index: 20; - width: auto; - height: auto; - clip: auto; - position: absolute; - overflow: hidden; - - // The following rules are to ensure that the element - // is visually hidden, unless it has focus. This is the recommended - // way to hide content from: - // https://webaim.org/techniques/css/invisiblecontent/#techniques - - &:not(:focus) { - clip: rect(1px, 1px, 1px, 1px); - clip-path: inset(50%); - height: 1px; - width: 1px; - margin: -1px; - padding: 0; - } -` - -function PageElement({element}) { - return ( - - - - {element} - - ) -} - export default PageElement diff --git a/theme/src/root-element.js b/theme/src/root-element.js index 05212773487..dc81ab31b3e 100644 --- a/theme/src/root-element.js +++ b/theme/src/root-element.js @@ -1,63 +1,41 @@ import React from 'react' import {MDXProvider} from '@mdx-js/react' -import {Link, SSRProvider, ThemeProvider, theme} from '@primer/react' -import Blockquote from './mdx/blockquote' -import Code from './mdx/code' -import DescriptionList from './mdx/description-list' -import {H1, H2, H3, H4, H5, H6} from './mdx/heading' -import HorizontalRule from './mdx/horizontal-rule' -import Image from './mdx/image' -import InlineCode from './mdx/inline-code' -import List from './mdx/list' -import Paragraph from './mdx/paragraph' -import Table from './mdx/table' -import Index from './mdx/index' -import Note from './mdx/note' -import Prompt from './mdx/prompt' -import PromptReply from './mdx/prompt-reply' -import Screenshot from './mdx/screenshot' - -function UnderlinedLink(props) { - return -} - -console.log(theme) +import {SSRProvider, ThemeProvider} from '@primer/react' +import * as Components from './mdx' const components = { - a: UnderlinedLink, - pre: props => props.children, - code: Code, - inlineCode: InlineCode, - table: Table, - img: Image, - p: Paragraph, - hr: HorizontalRule, - blockquote: Blockquote, - h1: H1, - h2: H2, - h3: H3, - h4: H4, - h5: H5, - h6: H6, - ul: List, - ol: List.withComponent('ol'), - dl: DescriptionList, - Index, - Note, - Prompt, - PromptReply, - Screenshot, - Link: UnderlinedLink, + a: Components.Link, + pre: Components.Pre, + code: Components.Code, + inlineCode: Components.InlineCode, + table: Components.Table, + img: Components.Image, + p: Components.Paragraph, + hr: Components.HorizontalRule, + blockquote: Components.Blockquote, + h1: Components.H1, + h2: Components.H2, + h3: Components.H3, + h4: Components.H4, + h5: Components.H5, + h6: Components.H6, + ul: Components.UnorderedList, + ol: Components.OrderedList, + dl: Components.DescriptionList, + Index: Components.Index, + Note: Components.Note, + Prompt: Components.Prompt, + PromptReply: Components.PromptReply, + Screenshot: Components.Screenshot, + Link: Components.Link, } -function RootElement({element}) { - return ( - - - {element} - - - ) -} +const RootElement = ({element}) => ( + + + {element} + + +) export default RootElement