1
1
mirror of https://github.com/primer/css.git synced 2024-09-11 16:36:07 +03:00

use blueprints!

This commit is contained in:
emplums 2019-04-08 15:11:08 -07:00
parent c501c50b19
commit 2bbe22661d
36 changed files with 15 additions and 1461 deletions

View File

@ -1,17 +0,0 @@
{
"extends": [
"plugin:github/react",
"plugin:jsx-a11y/recommended"
],
"rules": {
"import/no-namespace": 0,
"no-unused-vars": ["error", {
"ignoreRestSiblings": true
}]
},
"settings": {
"react": {
"version": "detect"
}
}
}

View File

@ -1,8 +0,0 @@
import {Box} from '@primer/components'
import styled from 'styled-components'
const BoxShadow = styled(Box)`
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
`
export default BoxShadow

View File

@ -1,47 +0,0 @@
import React from 'react'
import {findDOMNode} from 'react-dom'
import {Button} from '@primer/components'
import Octicon, {Clippy} from '@githubprimer/octicons-react'
export default class ClipboardCopy extends React.Component {
copy() {
const {value = ''} = this.props
const {clipboard} = window.navigator
const done = () => {
// eslint-disable-next-line react/no-find-dom-node
findDOMNode(this).dispatchEvent(new CustomEvent('copy', {bubbles: false}))
}
if (clipboard) {
return clipboard.writeText(value).then(done)
} else if (!document.body) {
return
} else {
const node = document.createElement('pre')
node.style.width = '1px'
node.style.height = '1px'
node.style.position = 'fixed'
node.style.top = '5px'
node.textContent = value
const selection = window.getSelection()
document.body.appendChild(node)
selection.removeAllRanges()
const range = document.createRange()
range.selectNodeContents(node)
selection.addRange(range)
document.execCommand('copy')
selection.removeAllRanges()
document.body.removeChild(node)
}
}
render() {
// eslint-disable-next-line no-unused-vars
const {value = '', ...rest} = this.props
return (
<Button onClick={() => this.copy()} type="button" {...rest}>
<Octicon icon={Clippy} />
</Button>
)
}
}

View File

@ -1,85 +0,0 @@
import React from 'react'
import HTMLtoJSX from 'html-2-jsx'
import styled from 'styled-components'
import {Absolute, BorderBox, Box, StyledOcticon as Octicon, Relative, Text} from '@primer/components'
import {LiveEditor, LiveError, LivePreview, LiveProvider} from 'react-live'
import {getIconByName} from '@githubprimer/octicons-react'
import ClipboardCopy from './ClipboardCopy'
import Frame from './Frame'
import CodeExampleStyles from './CodeExampleStyles'
const StyledLiveProvider = styled(LiveProvider)`
${CodeExampleStyles}
`
const LANG_PATTERN = /\blanguage-\.?(jsx|html)\b/
const converter = new HTMLtoJSX({
indent: ' ',
createClass: false
})
const defaultTransform = code => `<React.Fragment>${code}</React.Fragment>`
const languageTransforms = {
html: html => defaultTransform(converter.convert(html)),
jsx: defaultTransform
}
export default function CodeExample(props) {
const {children, dangerouslySetInnerHTML, inert, source, ...rest} = props
const lang = getLanguage(props.className)
if (lang && !inert) {
const liveProps = {
code: source,
scope: {Octicon, getIconByName},
transformCode: getTransformForLanguage(lang),
mountStylesheet: false
}
return (
<StyledLiveProvider {...liveProps}>
<BorderBox {...rest}>
<BorderBox bg="white" border={0} borderBottom={1} borderRadius={0}>
<Frame>
<LivePreview />
</Frame>
</BorderBox>
<Box as={Relative} bg="gray.1" p={3}>
<LiveEditor style={{margin: 0, padding: 0}} />
<Absolute right={0} top={0} m={3}>
<ClipboardCopy value={source} />
</Absolute>
<Text
as={LiveError}
fontFamily="mono"
style={{
overflow: 'auto',
whiteSpace: 'pre'
}}
/>
</Box>
</BorderBox>
</StyledLiveProvider>
)
} else {
const rest = {
children,
dangerouslySetInnerHTML
}
// eslint-disable-next-line react/no-danger-with-children
return <BorderBox data-source={source} as="pre" {...rest} />
}
}
CodeExample.defaultProps = {
my: 4
}
function getLanguage(className) {
const match = className && className.match(LANG_PATTERN)
return match ? match[1] : undefined
}
function getTransformForLanguage(lang) {
return lang in languageTransforms ? languageTransforms[lang] : null
}

View File

@ -1,80 +0,0 @@
const styles = `
code,
code[class*='language-'],
pre[class*='language-'] {
color: #333;
text-align: left;
white-space: pre;
word-spacing: normal;
tab-size: 4;
hyphens: none;
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
line-height: 1.4;
direction: ltr;
cursor: text;
}
pre[class*='language-'] {
overflow: auto;
margin: 1em 0;
padding: 1.2em;
border-radius: 3px;
font-size: 85%;
}
p code,
li code,
table code {
margin: 0;
border-radius: 3px;
padding: 0.2em 0;
font-size: 85%;
}
p code:before, p code:after,
li code:before,
li code:after,
table code:before,
table code:after {
letter-spacing: -0.2em;
content: "00a0";
}
code,
:not(pre) > code[class*='language-'],
pre[class*='language-'] {
background: #f7f7f7;
}
:not(pre) > code[class*='language-'] {
padding: 0.1em;
border-radius: 0.3em;
}
.token.comment, .token.prolog, .token.doctype, .token.cdata {
color: #969896;
}
.token.punctuation, .token.string, .token.atrule, .token.attr-value {
color: #183691;
}
.token.property, .token.tag {
color: #63a35c;
}
.token.boolean, .token.number {
color: #0086b3;
}
.token.selector, .token.attr-name, .token.attr-value .punctuation:first-child, .token.keyword, .token.regex, .token.important {
color: #a71d5d;
}
.token.operator, .token.entity, .token.url, .language-css .token.string {
color: #a71d5d;
}
.token.entity {
cursor: help;
}
.namespace {
opacity: 0.7;
}
`
export default styles

View File

@ -1,101 +0,0 @@
import React, {useEffect, useState} from 'react'
import PropTypes from 'prop-types'
import {Link, Text, Avatar, Flex} from '@primer/components'
function generateContributors(authors) {
const logins = []
const uniqueAuthors = authors.filter(author => {
if (logins.includes(author.login)) {
return false
} else {
logins.push(author.login)
return true
}
})
return uniqueAuthors.map((author, i) => (
<>
<Link href={`https://github.com/${author.login}`}>{author.login}</Link>
{authors.length > 1 && authors.length - 1 !== i && ', '}
</>
))
}
function generateLastEdited(lastAuthor) {
if (lastAuthor) {
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
const day = lastAuthor.time.getDate()
const month = months[lastAuthor.time.getMonth()]
const year = lastAuthor.time.getFullYear()
return (
<Flex alignItems="center">
<Text fontWeight="bold" lineHeight={2} mr={1}>
Last edited by:{' '}
</Text>
<Avatar src={lastAuthor.avatar} mr={1} />
<Text>
<Link href={`https://github.com/${lastAuthor.login}`}> {lastAuthor.login}</Link> on{' '}
<Link color="gray.5" href={lastAuthor.commitUrl}>{`${month} ${day}, ${year}`}</Link>
</Text>
</Flex>
)
}
}
const Contributors = ({filePath, repoPath, contributors}) => {
const [authors, setAuthors] = useState([])
useEffect(() => {
const url = `https://api.github.com/repos/${repoPath}/commits?path=${filePath}`
fetch(url)
.then(response => response.json())
.then(commits => {
const commitData = []
const ids = []
for (let i = 0; i < commits.length; i++) {
if (!ids.includes(commits[i].author.id)) {
commitData.push({
login: commits[i].author.login,
avatar: commits[i].author.avatar_url,
time: new Date(commits[i].commit.author.date),
commitUrl: commits[i].html_url
})
ids.push(commits[i].author.id)
}
}
setAuthors(commitData)
})
}, [filePath])
return (
<Text fontSize={1}>
<Text fontWeight="bold" lineHeight={2}>
Contributors:{' '}
</Text>
{generateContributors([...contributors, ...authors])}
{generateLastEdited(authors[0])}
</Text>
)
}
Contributors.defaultProps = {
contributors: []
}
Contributors.propTypes = {
contributors: PropTypes.object.isRequired,
filePath: PropTypes.string.isRequired,
repoPath: PropTypes.string.isRequired
}
export default Contributors

View File

@ -1,69 +0,0 @@
import React, {useState} from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import {theme} from '@primer/components'
import {space, color} from 'styled-system'
const DetailsReset = styled('details')`
cursor: pointer;
& > summary {
list-style: none;
}
& > summary::-webkit-details-marker {
display: none;
}
`
function getRenderer(children) {
return typeof children === 'function' ? children : () => children
}
function DetailsBase({children, overlay, render = getRenderer(children), ...rest}) {
const [open, setOpen] = useState(Boolean(rest.open))
function toggle(event) {
if (event) event.preventDefault()
if (overlay) {
openMenu()
} else {
setOpen(!open)
}
}
function openMenu() {
if (!open) {
setOpen(true)
document.addEventListener('click', closeMenu)
}
}
function closeMenu() {
setOpen(false)
document.removeEventListener('click', closeMenu)
}
return (
<DetailsReset {...rest} open={open} overlay={overlay}>
{render({open, toggle})}
</DetailsReset>
)
}
const Details = styled(DetailsBase)`
${space};
${color};
`
Details.defaultProps = {
theme,
overlay: false
}
Details.propTypes = {
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
className: PropTypes.string,
open: PropTypes.bool,
overlay: PropTypes.bool,
render: PropTypes.func,
theme: PropTypes.object
}
export default Details

View File

@ -1,66 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom'
import Measure from 'react-measure'
import {BorderBox} from '@primer/components'
import {assetPrefix} from './utils'
export default class Frame extends React.Component {
static defaultProps = {
border: 0,
borderRadius: 0,
minHeight: 0,
width: '100%'
}
constructor(props) {
super(props)
this.state = {files: [], height: props.height}
}
componentDidMount() {
this.doc = this.iframe.contentDocument
const files = JSON.parse(document.body.dataset.files || '[]')
// eslint-disable-next-line react/no-did-mount-set-state
this.setState({loaded: false, files})
this.iframe.addEventListener('load', () => {
this.setState({loaded: true})
})
}
getHead() {
const {files} = this.state
return files
? files
.filter(file => file.endsWith('.css'))
.map(file => <link rel="stylesheet" href={`${assetPrefix}/_next/${file}`} key={file} />)
: null
}
getBody(children) {
return (
<Measure bounds onResize={rect => this.setHeight(rect.bounds.height)}>
{({measureRef}) => <div ref={measureRef}>{children}</div>}
</Measure>
)
}
setHeight(height) {
// console.warn('height:', height)
this.setState({height})
}
render() {
const {children, ...rest} = this.props
const {height = 'auto'} = this.state
return (
<BorderBox as="iframe" style={{height}} {...rest} ref={node => (this.iframe = node)}>
{this.doc
? [
ReactDOM.createPortal(this.getHead(), this.doc.head),
ReactDOM.createPortal(this.getBody(children), this.doc.body)
]
: null}
</BorderBox>
)
}
}

View File

@ -1,71 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import {MarkGithub, ChevronRight} from '@githubprimer/octicons-react'
import {Text, Flex, Sticky, BorderBox, Box, StyledOcticon} from '@primer/components'
import Link from './Link'
import Search from './Search'
const BoxShadow = styled(Box)`
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
`
const HeaderText = props => <Text fontFamily="mono" fontSize={2} color="blue.4" {...props} />
const Header = ({title, subtitle, root, subfolder, documents, children}) => (
<Sticky zIndex={100}>
<BoxShadow py={3} bg="black" color="white">
<Flex alignItems="center" justifyContent="space-between">
<Flex alignItems="center">
<Link href={root} color="white" ml={5}>
<StyledOcticon color="blue.4" icon={MarkGithub} size="medium" />
</Link>
<Box display={['none', 'inline-block', 'inline-block', 'inline-block']}>
<Link href={root} mr={2} ml={3}>
<HeaderText>{title}</HeaderText>
</Link>
{subfolder && <StyledOcticon icon={ChevronRight} mx={1} color="blue.4" />}
</Box>
{subfolder && (
<Link href={`${root}/${subfolder}`} ml={2} mr={4}>
<HeaderText>{subtitle}</HeaderText>
</Link>
)}
{subfolder && (
<Box display={['none', 'none', 'none', 'flex']}>
<Search documents={documents} subfolder={subfolder} />
</Box>
)}
</Flex>
<Box display={['none', 'none', 'none', 'flex']}>{children}</Box>
<Box display={['flex', 'flex', 'flex', 'none']}>
<Link href="#jumpnav">
<BorderBox
border={1}
borderColor="gray.6"
borderRadius={3}
color="white"
display="inline-block"
px="12px"
py="6px"
mr={3}
>
<Text fontWeight="bold" color="blue.2" fontSize={1}>
Menu
</Text>
</BorderBox>
</Link>
{subfolder && <Search documents={documents} root={root} />}
</Box>
</Flex>
</BoxShadow>
</Sticky>
)
Header.propTypes = {
root: PropTypes.string.isRequired,
subfolder: PropTypes.string.isRequired,
title: PropTypes.string.isRequired
}
export default Header

View File

@ -1,28 +0,0 @@
import React from 'react'
import {Link} from '@primer/components'
import NavDropdown from './NavDropdown'
import NavItem from './NavItem'
const JumpNav = () => {
return (
<>
<Link color="blue.2" mx={3} href="https://primer.style/news">
Whats New
</Link>
<NavDropdown title="Design">
<NavItem href="https://primer.style/design">Interface Guidelines</NavItem>
<NavItem href="https://octicons.github.com/">Octicons</NavItem>
<NavItem href="https://primer.style/css/tools/prototyping">Prototyping</NavItem>
</NavDropdown>
<NavDropdown title="Development" direction="sw">
<NavItem href="https://primer.style/css">Primer CSS</NavItem>
<NavItem href="https://primer.style/components">Primer Components</NavItem>
</NavDropdown>
<Link color="blue.2" mr={3} href="https://primer.style/team">
Team
</Link>
</>
)
}
export default JumpNav

View File

@ -1,11 +0,0 @@
import React from 'react'
import NextLink from 'next/link'
import {Link as PrimerLink} from '@primer/components'
export default function Link({href, ...rest}) {
return (
<NextLink href={href}>
<PrimerLink href={href} {...rest} />
</NextLink>
)
}

View File

@ -1,31 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import {StyledOcticon, Heading, Box, Text} from '@primer/components'
import {Link} from '@githubprimer/octicons-react'
import slugify from 'slugify'
const Anchor = ({id}) => (
<a href={`#${id}`}>
<StyledOcticon color="black" icon={Link} />
</a>
)
const StyledHeading = styled(Heading)`
&:hover .anchorWrapper,
&:focus .anchorWrapper {
display: inline-block;
}
`
const MarkdownHeading = ({children, className, ...rest}) => {
const id = slugify(children.toString())
return (
<StyledHeading id={id} className={className} {...rest}>
<Box as={Text} lineHeight={1} className="anchorWrapper" pr={1} ml={'-20px'} display={'none'}>
<Anchor id={id} />
</Box>
{children}
</StyledHeading>
)
}
export default MarkdownHeading

View File

@ -1,69 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import {ChevronDown} from '@githubprimer/octicons-react'
import {Text, Flex, Relative, StyledOcticon, Absolute, Box} from '@primer/components'
import getDirectionStyles from './getDirectionStyles'
import Details from './Details'
const DropdownMenu = styled.div`
box-shadow: 0px 4px 12px rgba(27, 31, 35, 0.15);
border: 1px solid rgba(219, 237, 255, 0.3);
border-radius: 4px;
max-width: 215px;
overflow: hidden;
${props => (props.direction ? getDirectionStyles(props.theme, props.direction) : '')};
`
function NavDropdown({children, title, color, direction = 'se', ...rest}) {
return (
<Box {...rest}>
<Details
overlay
mx={3}
render={({toggle}) => (
<>
<Text color={color} as="summary" onClick={toggle}>
{title} <StyledOcticon icon={ChevronDown} />
</Text>
<Relative>
<DropdownMenu as={Absolute} py={2} zIndex={90} bg="black" direction={direction}>
{children}
</DropdownMenu>
</Relative>
</>
)}
/>
</Box>
)
}
const Responsive = ({children, title, color}) => {
return (
<Details
render={({toggle}) => (
<>
<Flex as="summary" alignItems="center" justifyContent="space-between" onClick={toggle}>
<Text fontWeight="bold" color={color}>
{title}
</Text>
<StyledOcticon icon={ChevronDown} />
</Flex>
<Relative>{children}</Relative>
</>
)}
/>
)
}
NavDropdown.Responsive = Responsive
NavDropdown.defaultProps = {
color: 'blue.2'
}
NavDropdown.propTypes = {
color: PropTypes.string
}
export default NavDropdown

View File

@ -1,20 +0,0 @@
import {theme, Link} from '@primer/components'
import styled from 'styled-components'
const NavItem = styled(Link)`
color: ${theme.colors.blue[2]};
display: block;
padding: 0 ${theme.space[3]}px;
line-height: 30px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 215px;
&:hover {
color: ${theme.colors.black};
background-color: ${theme.colors.blue[4]};
text-decoration: none;
}
`
export default NavItem

View File

@ -1,14 +0,0 @@
import React from 'react'
import {withRouter} from 'next/router'
import {Box} from '@primer/components'
import PageLink from './PageLink'
const NavLink = withRouter(({href, router, ...rest}) => {
return (
<Box {...rest}>
<PageLink href={href} color={router.pathname === href ? 'black' : undefined} fontSize={1} {...rest} />
</Box>
)
})
export default NavLink

View File

@ -1,25 +0,0 @@
import React from 'react'
import Pages from '@primer/next-pages'
import {nodeSort} from './utils'
import NavLink from './NavLink'
import SectionLink from './SectionLink'
const {pageMap = new Map()} = Pages
/**
* A <NavList> renders a <Section.Link> for the given `path` and looks up the
* path in the page tree. If a node is found, it renders a <NavLink> for each
* of the node's children.
*/
export default function NavList({path}) {
const node = pageMap.get(path)
const children = node ? node.children.sort(nodeSort) : []
return (
<>
<SectionLink color="gray.9" href={path} mb={3} />
{children.map(child => (
<NavLink mt={2} href={child.path} key={child.path} />
))}
</>
)
}

View File

@ -1,33 +0,0 @@
import React from 'react'
import Link from './Link'
import {rootPage} from './utils'
/**
* The NodeLink component takes an `href` and optional `children`.
* If no `children` are provided, we look up the "node" of the corresponding
* page in the tree (the one whose `path` matches the given `href`) and use
* that node's `meta.title` if it's set. In other words, given the following
* pages/foo/bar.md:
*
* ```md
* ---
* title: Foo Bar
* ---
* ```
*
* The following instance of NodeLink should render a link to "/foo/bar" with
* "Foo Bar" as its text:
*
* ```jsx
* <NodeLink href="/foo/bar" />
* ```
*/
export default function NodeLink(props) {
const {href, children: content} = props
if (content) {
return <Link {...props} />
}
const node = rootPage.first(node => node.path === href)
const children = (node ? node.meta.title : null) || href
return <Link {...props}>{children}</Link>
}

View File

@ -1,39 +0,0 @@
import React from 'react'
import Link from './Link'
import Pages from '@primer/next-pages'
const {pageMap = new Map()} = Pages
/**
* The PageLink component takes an `href` and optional `children`.
* If no `children` are provided, we look up the "node" of the corresponding
* page in the tree (the one whose `path` matches the given `href`) and use
* that page's `meta.title` if it's set. In other words, given the following
* pages/foo/bar.md:
*
* ```md
* ---
* title: Foo Bar
* ---
* ```
*
* The following instance of PageLink should render a link to "/foo/bar" with
* "Foo Bar" as its text:
*
* ```jsx
* <PageLink href="/foo/bar" />
* ```
*/
export default function PageLink(props) {
const {href, children: content} = props
if (content) {
return <Link {...props} />
}
const page = pageMap.get(href)
if (!page) {
// eslint-disable-next-line no-console
console.warn(`no page for "${href}"`, pageMap.keys())
}
const children = (page ? page.meta.title : null) || href
return <Link {...props}>{children}</Link>
}

View File

@ -1,46 +0,0 @@
import React from 'react'
import {Link as PrimerLink, BorderBox, Box} from '@primer/components'
import NavDropdown from './NavDropdown'
import Link from './Link'
const DropdownLink = ({href, children}) => (
<Box mt={2}>
<Link fontSize={1} href={href}>
{children}
</Link>
</Box>
)
const ResponsiveJumpNav = () => {
return (
<BorderBox p={5} bg="gray.1" border={0} borderBottom={1} borderColor="gray.2" borderRadius={0} width="100%">
<BorderBox py={3} borderColor="gray.2" borderTop={0} borderLeft={0} borderRight={0} borderBottom={1}>
<PrimerLink color="black" fontWeight="bold" my={2} href="https://primer.style/news">
Whats New
</PrimerLink>
</BorderBox>
<BorderBox py={3} borderColor="gray.2" borderTop={0} borderLeft={0} borderRight={0} borderBottom={1}>
<PrimerLink color="black" fontWeight="bold" my={2} href="https://primer.style/team">
Team
</PrimerLink>
</BorderBox>
<BorderBox py={3} borderColor="gray.2" borderTop={0} borderLeft={0} borderRight={0} borderBottom={1}>
<NavDropdown.Responsive color="black" title="Design">
<DropdownLink href="https://primer.style/design">Interface Guidelines</DropdownLink>
<DropdownLink href="https://octicons.github.com/">Icons</DropdownLink>
<DropdownLink href="https://github.com/primer/presentations">Presentations</DropdownLink>
</NavDropdown.Responsive>
</BorderBox>
<Box pt={3}>
<NavDropdown.Responsive color="black" title="Development">
<DropdownLink href="https://primer.style/css">Primer CSS</DropdownLink>
<DropdownLink href="https://primer.style/components">Primer Components</DropdownLink>
<DropdownLink href="https://github.com/primer/deploy">Deploy</DropdownLink>
<DropdownLink href="https://primer.style/css/tools/prototyping">Prototyping</DropdownLink>
</NavDropdown.Responsive>
</Box>
</BorderBox>
)
}
export default ResponsiveJumpNav

View File

@ -1,64 +0,0 @@
import {StyledOcticon} from '@primer/components'
import {Search, X} from '@githubprimer/octicons-react'
import React, {useState} from 'react'
import styled from 'styled-components'
import SearchInput from './SearchInput'
const Wrapper = styled.div`
display: flex;
position: relative;
${props =>
props.open &&
`
width: 100%;
position: absolute;
right: 0;
top: 0;
background: #1b1f23;
padding: 16px 0 16px 16px;
`};
`
const StyledButton = styled.button`
background: none;
display: inline-block;
color: #c8e1ff;
margin-right: 16px;
padding-left: 12px;
padding-right: 12px;
padding-top: 8px;
padding-bottom: 8px;
border: 1px solid;
border-color: #586069;
border-radius: 3px;
height: 38px;
right: 0px;
&:hover,
&:focus {
color: #c8e1ff;
background-color: #0366d6;
background-image: none;
border-color: #0366d6;
}
`
const ResponsiveSearchInput = props => {
const [open, setOpen] = useState(false)
const handleClick = () => {
if (open) {
props.closeMenu()
}
setOpen(!open)
}
return (
<Wrapper open={open}>
{open && <SearchInput mr={3} width="100%" {...props} />}
<StyledButton onClick={handleClick}>
<StyledOcticon icon={open ? X : Search} />
</StyledButton>
</Wrapper>
)
}
export default ResponsiveSearchInput

View File

@ -1,17 +0,0 @@
import React from 'react'
import {addPath} from './utils'
/**
* <RouteMatch> is just a way to conditionally render content without a wrapper
* element when contained directly in a <Router>:
*
* ```jsx
* <Router>
* <RouteMatch path="/some/dir">
* this will only show up on pages whose path begins with "/some/dir"
* </RouteMatch>
* </Router>
* ```
*/
export default function RouteMatch({path, children}) {
return path ? React.Children.map(children, child => addPath(child, path)) : children
}

View File

@ -1,23 +0,0 @@
import React from 'react'
import {withRouter} from 'next/router'
/**
* This is inspired by React Router's <Router> component, in that it looks for
* children with the `path` prop and only renders the _first_ one that matches
* the beginning of the current path. Children without a `path` prop are always
* rendered.
*/
const Router = withRouter(({router, children}) => {
let matched = false
return React.Children.toArray(children).map(child => {
if (child.props.path) {
if (!matched && router.pathname.indexOf(child.props.path) === 0) {
return (matched = child)
}
} else {
return child
}
})
})
export default Router

View File

@ -1,136 +0,0 @@
// downshift applies htmlFor for the label, but eslint doesn't recognize that
// also disabling the specific line causes some weirdness when running lint for syntax
/* eslint-disable jsx-a11y/label-has-for */
import React, {useState} from 'react'
import Router from 'next/router'
import lunr from 'lunr'
import {Relative, Box, Text} from '@primer/components'
import SearchItem from './SearchItem'
import Downshift from 'downshift'
import SearchInput from './SearchInput'
import SearchResults from './SearchResults'
import ResponsiveSearchInput from './ResponsiveSearchInput'
const generateBreadcrumb = path => {
const a = path
.toLowerCase()
.split('-')
.join(' ')
const b = a.split('/').join(' / ')
return b
.split(' ')
.map(s => s.charAt(0).toUpperCase() + s.substring(1))
.join(' ')
}
function Search({subfolder, documents}) {
const idx = lunr(function() {
this.ref('path') //eslint-disable-line
this.field('title')//eslint-disable-line
this.field('content')//eslint-disable-line
this.field('keywords')//eslint-disable-line
for (const doc of Object.values(documents)) {
this.add(doc) //eslint-disable-line
}
})
const [results, setResults] = useState([])
const onChange = e => {
if (e.target) {
setResults(idx.search(`${e.target.value}*`))
}
}
const renderResults = (selectedItem, getItemProps, highlightedIndex) => {
if (results.length < 1) {
return (
<Text as={Relative} p={2} color="black">
No results found
</Text>
)
}
return results.map((result, index) => {
const doc = documents[result.ref]
if (!doc.path) return
return (
<SearchItem //eslint-disable-line
{...getItemProps({
item: result,
index,
key: result.ref,
href: `/${subfolder}/${doc.path}`,
isHighlighted: highlightedIndex === index
})}
>
{doc.path && (
<Text as="div" fontSize={0}>
{generateBreadcrumb(doc.path)}
</Text>
)}
{doc.title}
</SearchItem>
)
})
}
function stateReducer(state, changes) {
switch (changes.type) {
case Downshift.stateChangeTypes.keyDownEnter:
case Downshift.stateChangeTypes.clickItem:
return {
...changes,
inputValue: ''
}
default:
return changes
}
}
const onSelect = item => {
Router.push(`/${subfolder}/${item.ref}`)
}
return (
<div>
<Downshift
onChange={onChange}
itemToString={item => (item ? documents[item.ref].title : '')}
stateReducer={stateReducer}
onSelect={onSelect}
>
{({
getInputProps,
getMenuProps,
getLabelProps,
closeMenu,
getItemProps,
isOpen,
highlightedIndex,
selectedItem
}) => (
<div>
<label hidden {...getLabelProps()}>
{' '}
Search docs
</label>
<Box display={['none', 'none', 'none', 'flex']}>
<SearchInput placeholder="Search" {...getInputProps({onChange})} />
</Box>
<Box
display={['inline-block', 'inline-block', 'inline-block', 'none']}
width={['100%', '100%', '100%', 'initial']}
>
<ResponsiveSearchInput closeMenu={closeMenu} {...getInputProps({onChange})} />
</Box>
<SearchResults color="black" open={isOpen} {...getMenuProps()}>
{renderResults(selectedItem, getItemProps, highlightedIndex)}
</SearchResults>
</div>
)}
</Downshift>
</div>
)
}
export default Search

View File

@ -1,19 +0,0 @@
import {TextInput} from '@primer/components'
import {space, width} from 'styled-system'
import styled from 'styled-components'
const SearchInput = styled(TextInput)`
color: #fff;
background-color: rgba(255, 255, 255, 0.07);
border: 1px;
border: 1px solid transparent;
&:focus,
&:active {
border: 1px solid rgba(255, 255, 255, 0.15);
outline: none;
box-shadow: none;
}
${space};
${width};
`
export default SearchInput

View File

@ -1,15 +0,0 @@
import styled from 'styled-components'
const SearchItem = styled.a`
display: block;
text-decoration: none;
color: ${props => (props.isHighlighted ? '#fff' : '#24292e')};
background-color: ${props => (props.isHighlighted ? '#0366d6' : '#fff')};
padding: 8px;
&:hover {
text-decoration: none;
}
`
export default SearchItem

View File

@ -1,25 +0,0 @@
import styled from 'styled-components'
const SearchResults = styled.div`
box-shadow: 0 1px 5px rgba(27, 31, 35, 0.15);
border: 1px solid #e1e4e8;
min-width: 320px;
border-radius: 3px;
overflow: auto;
max-height: 90vh;
overflow-y: scroll;
background-color: #fff;
display: ${props => (props.open ? 'block' : 'none')};
position: absolute;
@media (max-width: 1012px) {
width: 100%;
max-height: inherit;
height: 100vh;
margin-top: 30px;
right: 0;
left: 0;
}
`
export default SearchResults

View File

@ -1,28 +0,0 @@
import React from 'react'
import {BorderBox} from '@primer/components'
import NavList from './NavList'
import {addPath} from './utils'
/**
* A <Section> gets a `path` and optional children. If it has children it will
* render those and prepend each child's `href` prop with the provided `path`.
* This means that you can do:
*
* ```jsx
* <Section path="/section">
* <Link href="foo">Links to /section/foo</Link>
* </Section>
* ```
*
* If no children are provided, it renders a <NavList> with the provided
* `path`.
*/
const Section = ({path, children}) => {
return (
<BorderBox p={5} border={0} borderBottom={1} borderRadius={0} width="100%">
{children && path ? React.Children.map(children, child => addPath(child, path)) : <NavList path={path} />}
</BorderBox>
)
}
export default Section

View File

@ -1,12 +0,0 @@
import React from 'react'
import {withRouter} from 'next/router'
import {Box} from '@primer/components'
import PageLink from './PageLink'
const SectionLink = withRouter(({href, router, ...rest}) => (
<Box {...rest}>
<PageLink href={href} {...rest} fontSize={2} fontWeight={router.pathname.startsWith(href) ? 'bold' : null} />
</Box>
))
export default SectionLink

View File

@ -1,19 +0,0 @@
import React from 'react'
import {Box, Flex, Relative} from '@primer/components'
import Router from './Router'
function SideNav(props) {
return (
<Box height="100vh">
<Relative as="nav">
<Box {...props}>
<Flex flexDirection="column" alignItems="start">
<Router>{props.children}</Router>
</Flex>
</Box>
</Relative>
</Box>
)
}
export default SideNav

View File

@ -1,28 +1,2 @@
export {default as ClipboardCopy} from './ClipboardCopy'
export {default as CodeExample} from './CodeExample'
export {default as Contributors} from './Contributors'
export {default as Details} from './Details'
export {default as Frame} from './Frame'
export {default as Header} from './Header'
export {default as Link} from './Link'
export {default as MarkdownHeading} from './MarkdownHeading'
export {default as NavDropdown} from './NavDropdown'
export {default as NavItem} from './NavItem'
export {default as NavLink} from './NavLink'
export {default as NavList} from './NavList'
export {default as Outline} from './Outline'
export {default as PackageHeader} from './PackageHeader'
export {default as PageLink} from './PageLink'
export {default as JumpNav} from './JumpNav'
export {default as ResponsiveJumpNav} from './ResponsiveJumpNav'
export {default as RouteMatch} from './RouteMatch'
export {default as Router} from './Router'
export {default as Section} from './Section'
export {default as Search} from './Search'
export {default as SearchInput} from './SearchInput'
export {default as ResponsiveSearchInput} from './ResponsiveSearchInput'
export {default as SearchItem} from './SearchItem'
export {default as SearchResults} from './SearchResults'
export {default as SectionLink} from './SectionLink'
export {default as SideNav} from './SideNav'
export {default as StatusLabel} from './StatusLabel'

View File

@ -1,127 +0,0 @@
import {get} from './constants'
const getDirectionStyles = (theme, direction) => {
const map = {
w: `
top: 0;
right: 100%;
left: auto;
width: auto;
margin-top: 0;
margin-right: 10px;
&::before {
top: 10px;
right: -16px;
left: auto;
border-color: transparent;
border-left-color: ${get('colors.blackfade15')(theme)};
}
&::after {
top: 11px;
right: -14px;
left: auto;
border-color: transparent;
border-left-color: ${get('colors.white')(theme)};
}
`,
e: `
top: 0;
left: 100%;
width: auto;
margin-top: 0;
margin-left: 10px;
&::before {
top: 10px;
left: -16px;
border-color: transparent;
border-right-color: ${get('colors.blackfade15')(theme)};
}
&::after {
top: 11px;
left: -14px;
border-color: transparent;
border-right-color: ${get('colors.white')(theme)};
}
`,
ne: `
top: auto;
bottom: 100%;
left: 0;
margin-bottom: 3px;
&::before,
&::after {
top: auto;
right: auto;
}
&::before {
bottom: -8px;
left: 9px;
border-top: 8px solid ${get('colors.blackfade15')(theme)};
border-bottom: 0;
border-left: 8px solid transparent;
}
&::after {
bottom: -7px;
left: 10px;
border-top: 7px solid ${get('colors.white')(theme)};
border-right: 7px solid transparent;
border-bottom: 0;
border-left: 7px solid transparent;
}
`,
s: `
right: 50%;
left: auto;
transform: translateX(50%);
&::before {
top: -16px;
right: 50%;
transform: translateX(50%);
}
&::after {
top: -14px;
right: 50%;
transform: translateX(50%);
}
`,
sw: `
right: 0;
left: auto;
&::before {
top: -16px;
right: 9px;
left: auto;
}
&::after {
top: -14px;
right: 10px;
left: auto;
}
`,
se: `
&::before {
top: -16px;
left: 9px;
}
&::after {
top: -14px;
left: 10px;
}
`
}
return map[direction]
}
export default getDirectionStyles

View File

@ -1,43 +0,0 @@
import Router from 'next/router'
/**
* Export this as your default from a page, and it'll redirect both server-
* and client-side:
*
* ```js
* import {redirect} from '../src/utils'
* export default redirect('/some/path')
* ```
*/
export default function redirect(uri, status = 303) {
// XXX this doesn't need to extend React.Component because
// it doesn't "do" anything React-y
return class {
static getInitialProps({res}) {
// the "context" object passed to getInitialProps() will
// have a "res" (response) object if we're server-side
if (res) {
res.writeHead(status, {Location: uri})
res.end()
}
}
render() {
Router.replace(uri)
return null
}
}
}
export function redirectTrailingSlash(context, status = 301) {
const {
req: {url},
res
} = context
if (url.endsWith('/')) {
const withoutSlash = url.substr(0, url.length - 1)
res.writeHead(status, {Location: withoutSlash})
res.end()
return true
}
}

41
package-lock.json generated
View File

@ -1366,9 +1366,9 @@
}
},
"@primer/blueprints": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@primer/blueprints/-/blueprints-3.0.1.tgz",
"integrity": "sha512-B+YBA/S9GpkdC2PqKHsLqbjIXf7MjO+6Cl7jgDyvI6dg3PuNld7JmZSAGzV9NPbK6gI9F4yI9KIl4iSVPjSBgw==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@primer/blueprints/-/blueprints-3.0.2.tgz",
"integrity": "sha512-mEx3F1Fl8lrZTfahgG2mTwm51OQbbgxH95GcShHIQBxg/hhdV6ce11/01geeMf8P8I8okvBD+Wj88Arkcor30A==",
"dev": true,
"requires": {
"@githubprimer/octicons-react": "^8.1.3",
@ -1376,15 +1376,13 @@
"@primer/next-pages": "0.0.3",
"downshift": "3.2.7",
"globby": "9.1.0",
"html-2-jsx": "^0.5.1-dev",
"lunr": "2.3.6",
"prism-github": "^1.1.0",
"prop-types": "^15.6.2",
"react": "16.8.0",
"react-dom": "16.8.1",
"react-live": "2.0.0",
"react-measure": "^2.2.2",
"slugify": "1.3.4",
"styled-components": "4.1.3",
"styled-system": "4.0.4",
"val-loader": "1.1.1"
},
@ -1411,18 +1409,6 @@
"slash": "^2.0.0"
}
},
"react": {
"version": "16.8.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.8.0.tgz",
"integrity": "sha512-g+nikW2D48kqgWSPwNo0NH9tIGG3DsQFlrtrQ1kj6W77z5ahyIHG0w8kPpz4Sdj6gyLnz0lEd/xsjOoGge2MYQ==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.13.0"
}
},
"react-live": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-live/-/react-live-2.0.0.tgz",
@ -1438,25 +1424,6 @@
"react-simple-code-editor": "^0.9.0",
"unescape": "^0.2.0"
}
},
"styled-components": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.1.3.tgz",
"integrity": "sha512-0quV4KnSfvq5iMtT0RzpMGl/Dg3XIxIxOl9eJpiqiq4SrAmR1l1DLzNpMzoy3DyzdXVDMJS2HzROnXscWA3SEw==",
"dev": true,
"requires": {
"@babel/helper-module-imports": "^7.0.0",
"@emotion/is-prop-valid": "^0.7.3",
"@emotion/unitless": "^0.7.0",
"babel-plugin-styled-components": ">= 1",
"css-to-react-native": "^2.2.2",
"memoize-one": "^4.0.0",
"prop-types": "^15.5.4",
"react-is": "^16.6.0",
"stylis": "^3.5.0",
"stylis-rule-sheet": "^0.0.10",
"supports-color": "^5.5.0"
}
}
}
},

View File

@ -47,6 +47,9 @@
"@githubprimer/octicons-react": "^8.1.3",
"@mdx-js/mdx": "^0.16.6",
"@mdx-js/tag": "0.15.0",
"@primer/blueprints": "3.0.2",
"@primer/components": "12.0.1",
"@primer/next-pages": "0.0.3",
"@storybook/addon-viewport": "5.0.0",
"@storybook/react": "5.0.0",
"@svgr/webpack": "2.4.1",
@ -67,6 +70,7 @@
"eslint": "4.19.1",
"eslint-plugin-github": "1.0.0",
"execa": "^0.10.0",
"fs": "0.0.1-security",
"fs-extra": "^4.0.2",
"fx": "11.0.1",
"gh-pages": "^1.0.0",
@ -74,6 +78,7 @@
"gray-matter": "^4.0.1",
"hast-util-to-html": "^5.0.0",
"hast-util-to-string": "^1.0.1",
"html-2-jsx": "0.5.1-dev",
"html-to-react": "^1.2.11",
"klaw": "3.0.0",
"loader-utils": "^1.1.0",
@ -94,7 +99,9 @@
"prism-github": "^1.1.0",
"prop-types": "^15.6.2",
"raw-loader": "^0.5.1",
"react": "16.8.1",
"react-bodymovin": "2.0.0",
"react-dom": "16.8.1",
"react-live": "1.12.0",
"react-measure": "^2.2.2",
"refractor": "^2.6.2",
@ -117,13 +124,6 @@
"unist-util-select": "^2.0.0",
"unist-util-stringify-position": "^2.0.0",
"unist-util-visit": "^1.4.0",
"webpack": "4.20.2",
"@primer/components": "12.0.1",
"@primer/next-pages": "0.0.3",
"fs": "0.0.1-security",
"html-2-jsx": "0.5.1-dev",
"react": "16.8.1",
"react-dom": "16.8.1",
"@primer/blueprints": "3.0.1"
"webpack": "4.20.2"
}
}

View File

@ -3,7 +3,8 @@ import App, {Container} from 'next/app'
import {MDXProvider} from '@mdx-js/tag'
import Head from 'next/head'
import {BaseStyles, BorderBox, Box, Flex, theme} from '@primer/components'
import {Header, JumpNav, Section, RouteMatch, SectionLink, PackageHeader, SideNav} from '../docs/components'
import {PackageHeader} from '../docs/components'
import {Header, JumpNav, Section, RouteMatch, SectionLink, SideNav} from '@primer/blueprints'
import getComponents from '../docs/markdown'
import documents from '../searchIndex'
import {config, requirePage, rootPage} from '../docs/utils'

View File

@ -1,6 +1,6 @@
import React from 'react'
import {Heading} from '@primer/components'
import {redirectTrailingSlash} from '../docs/redirect'
import {redirectTrailingSlash} from '@primer/blueprints'
export default class extends React.Component {
static getInitialProps(context) {