Rework styling using styled-components (#2517)

The CSS in the project is relatively difficult to change. While it is very DRY, it relies heavily on inheritance. It's difficult to make changes in the markup modal without it also affecting styles elsewhere.
 
[styled-components](https://www.styled-components.com/) is one of the leading CSS-in-JS libraries. By reducing dependency on global state and CSS inheritance, styles become explicit and are easier to inspect and change. It's also convenient that styles can be embedded with the components they modify.

At runtime, the library creates CSS classes, so it's pretty efficient.

We were using a little bit of [styled-jsx](https://github.com/zeit/styled-jsx) before, which ships with Next.js, though styled-components is more widely used and I've had good experiences with it all around.

In a few cases I've duplicated styles where it feels more natural to do that: for example, `text-align: center` is duplicated in `Main` and `MarkupModal`.

Much of this is a refactor, though there are a few visual changes, particularly in the markup modal and the style examples.
This commit is contained in:
Paul Melnikow
2018-12-18 16:44:47 -05:00
committed by GitHub
parent a1150efd25
commit 5c7d07f5be
20 changed files with 688 additions and 439 deletions

View File

@@ -35,7 +35,8 @@ const helperTests = fileMatch('lib/**/*.spec.js')
const packageJson = fileMatch('package.json')
const packageLock = fileMatch('package-lock.json')
const capitals = fileMatch('**/*[A-Z]*.js')
const underscores = fileMatch('**/*_*.js')
// _document.js is used by convention by Next.
const underscores = fileMatch('**/*_*.js', '!pages/_document.js')
const targetBranch = danger.github.pr.base.ref
message(

View File

@@ -1,6 +1,27 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { badgeUrlFromPath, staticBadgeUrl } from '../../lib/make-badge-url'
import { Badge } from './common'
import { StyledCode } from './snippet'
const ExampleTable = styled.table`
min-width: 50%;
margin: auto;
th,
td {
text-align: left;
}
`
const ClickableTh = styled.th`
cursor: pointer;
`
const ClickableCode = styled(StyledCode)`
cursor: pointer;
`
export default class BadgeExamples extends React.Component {
static propTypes = {
@@ -47,21 +68,12 @@ export default class BadgeExamples extends React.Component {
return (
<tr key={key}>
<th className="clickable" onClick={handleClick}>
{title}:
</th>
<ClickableTh onClick={handleClick}>{title}:</ClickableTh>
<td>
<img
className="badge-img clickable"
onClick={handleClick}
src={previewUrl}
alt=""
/>
<Badge clickable onClick={handleClick} src={previewUrl} />
</td>
<td>
<code className="clickable" onClick={handleClick}>
{exampleUrl}
</code>
<ClickableCode onClick={handleClick}>{exampleUrl}</ClickableCode>
</td>
</tr>
)
@@ -80,13 +92,11 @@ export default class BadgeExamples extends React.Component {
}, [])
return (
<div>
<table className="badge">
<tbody>
{flattened.map(exampleData => this.renderExample(exampleData))}
</tbody>
</table>
</div>
<ExampleTable>
<tbody>
{flattened.map(exampleData => this.renderExample(exampleData))}
</tbody>
</ExampleTable>
)
}
}

View File

@@ -1,13 +1,14 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Link } from 'react-router-dom'
import { H3 } from './common'
const CategoryHeading = ({ category }) => {
const { id, name } = category
return (
<Link to={`/examples/${id}`}>
<h3 id={id}>{name}</h3>
<H3 id={id}>{name}</H3>
</Link>
)
}

View File

@@ -2,6 +2,7 @@ import React from 'react'
import { shallow } from 'enzyme'
import { expect } from 'chai'
import { CategoryHeading, CategoryHeadings } from './category-headings'
import { H3 } from './common'
import './enzyme-conf.spec'
@@ -14,7 +15,7 @@ describe('<CategoryHeading />', function() {
it('contains the expected heading', function() {
const wrapper = shallow(<CategoryHeading category={exampleCategories[0]} />)
expect(wrapper).to.contain(<h3 id="cat">Example category</h3>)
expect(wrapper).to.contain(<H3 id="cat">Example category</H3>)
})
})

View File

@@ -0,0 +1,97 @@
import React from 'react'
import styled, { css } from 'styled-components'
const nonBreakingSpace = '\u00a0'
const BaseFont = styled.div`
font-family: Lekton, sans-serif;
color: #534;
`
const H2 = styled.h2`
font-style: italic;
margin-top: 12mm;
font-variant: small-caps;
::before {
content: '☙ ';
}
::after {
content: ' ❧';
}
`
const H3 = styled.h3`
font-style: italic;
`
const BadgeWrapper = styled.span`
padding: 2px;
height: ${({ height }) => height};
vertical-align: middle;
display: ${({ display }) => display};
${({ clickable }) =>
clickable &&
css`
cursor: pointer;
`};
`
const Badge = ({
src,
alt = '',
display = 'inline',
height = '20px',
clickable = false,
...rest
}) => (
<BadgeWrapper height={height} clickable={clickable} display={display}>
{src ? <img src={src} alt={alt} {...rest} /> : nonBreakingSpace}
</BadgeWrapper>
)
const StyledInput = styled.input`
height: 15px;
border: solid #b9a;
border-width: 0 0 1px 0;
padding: 0;
text-align: center;
color: #534;
:focus {
outline: 0;
}
`
const InlineInput = styled(StyledInput)`
width: 70px;
margin-left: 5px;
margin-right: 5px;
`
const BlockInput = styled(StyledInput)`
width: 40%;
background-color: transparent;
`
const VerticalSpace = styled.hr`
border: 0;
display: block;
height: 3mm;
`
export {
nonBreakingSpace,
BaseFont,
H2,
H3,
Badge,
InlineInput,
BlockInput,
VerticalSpace,
}

View File

@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { dynamicBadgeUrl } from '../lib/badge-url'
import { InlineInput } from './common'
export default class DynamicBadgeMaker extends React.Component {
static propTypes = {
@@ -52,49 +53,42 @@ export default class DynamicBadgeMaker extends React.Component {
<option value="xml">xml</option>
<option value="yaml">yaml</option>
</select>{' '}
{}
<input
<InlineInput
className="short"
value={this.state.label}
onChange={event => this.setState({ label: event.target.value })}
placeholder="label"
/>{' '}
{}
<input
/>
<InlineInput
className="short"
value={this.state.url}
onChange={event => this.setState({ url: event.target.value })}
placeholder="url"
/>{' '}
{}
<input
/>
<InlineInput
className="short"
value={this.state.query}
onChange={event => this.setState({ query: event.target.value })}
placeholder="query"
/>{' '}
{}
<input
/>
<InlineInput
className="short"
value={this.state.color}
onChange={event => this.setState({ color: event.target.value })}
placeholder="color"
/>{' '}
{}
<input
/>
<InlineInput
className="short"
value={this.state.prefix}
onChange={event => this.setState({ prefix: event.target.value })}
placeholder="prefix"
/>{' '}
{}
<input
/>
<InlineInput
className="short"
value={this.state.suffix}
onChange={event => this.setState({ suffix: event.target.value })}
placeholder="suffix"
/>{' '}
{}
/>
<button disabled={!this.isValid}>Make Badge</button>
</form>
)

View File

@@ -1,10 +1,17 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import resolveUrl from '../lib/resolve-url'
import { H2 } from './common'
const SpacedA = styled.a`
margin-left: 10px;
margin-right: 10px;
`
const Footer = ({ baseUrl }) => (
<section>
<h2 id="like-this">Like This?</h2>
<H2 id="like-this">Like This?</H2>
<p>
<object
@@ -49,9 +56,9 @@ const Footer = ({ baseUrl }) => (
and we might bring it to you!
</p>
<p className="spaced-row">
<a href="https://status.shields.io/">Status</a>
<a href="https://github.com/badges/shields/">GitHub</a>
<p>
<SpacedA href="https://status.shields.io/">Status</SpacedA>
<SpacedA href="https://github.com/badges/shields/">GitHub</SpacedA>
</p>
</section>
)

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { shallow } from 'enzyme'
import { shallow, render } from 'enzyme'
import { expect } from 'chai'
import Footer from './footer'
@@ -11,7 +11,7 @@ describe('<Footer />', function() {
})
it('contains a link to the status page', function() {
const wrapper = shallow(<Footer baseUrl="https://example.shields.io" />)
expect(wrapper).to.contain(<a href="https://status.shields.io/">Status</a>)
const wrapper = render(<Footer baseUrl="https://example.shields.io" />)
expect(wrapper.html()).to.contain('https://status.shields.io/')
})
})

View File

@@ -1,5 +1,11 @@
import { Link } from 'react-router-dom'
import React from 'react'
import styled from 'styled-components'
import { VerticalSpace } from './common'
const Highlights = styled.p`
font-style: italic;
`
export default () => (
<section>
@@ -7,17 +13,11 @@ export default () => (
<img alt="Shields.io" src="/static/logo.svg" />
</Link>
<hr className="spacing" />
<VerticalSpace />
<p className="highlights">
<Highlights>
Pixel-perfect &nbsp; Retina-ready &nbsp; Fast &nbsp; Consistent &nbsp;
Hackable &nbsp; No tracking
</p>
<style jsx>{`
.highlights {
font-style: italic;
}
`}</style>
</Highlights>
</section>
)

View File

@@ -1,5 +1,15 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import groupBy from 'lodash.groupby'
import {
categories,
findCategory,
services,
getDefinitionsForCategory,
} from '../lib/service-definitions'
import ServiceDefinitionSetHelper from '../lib/service-definitions/service-definition-set-helper'
import { baseUrl, longCache } from '../constants'
import Meta from './meta'
import Header from './header'
import SuggestionAndSearch from './suggestion-and-search'
@@ -8,16 +18,12 @@ import MarkupModal from './markup-modal'
import Usage from './usage'
import Footer from './footer'
import { CategoryHeading, CategoryHeadings } from './category-headings'
import {
categories,
findCategory,
services,
getDefinitionsForCategory,
} from '../lib/service-definitions'
import BadgeExamples from './badge-examples'
import { baseUrl, longCache } from '../constants'
import ServiceDefinitionSetHelper from '../lib/service-definitions/service-definition-set-helper'
import groupBy from 'lodash.groupby'
import { BaseFont } from './common'
const AppContainer = styled(BaseFont)`
text-align: center;
`
export default class Main extends React.Component {
constructor(props) {
@@ -140,7 +146,7 @@ export default class Main extends React.Component {
const { selectedExample } = this.state
return (
<div id="app">
<AppContainer id="app">
<Meta />
<Header />
<MarkupModal
@@ -161,7 +167,7 @@ export default class Main extends React.Component {
{this.renderMain()}
<Usage baseUrl={baseUrl} longCache={longCache} />
<Footer baseUrl={baseUrl} />
</div>
</AppContainer>
)
}
}

View File

@@ -1,12 +1,24 @@
import React from 'react'
import PropTypes from 'prop-types'
import Modal from 'react-modal'
import ClickToSelect from '@mapbox/react-click-to-select'
import styled from 'styled-components'
import { badgeUrlFromPath, badgeUrlFromPattern } from '../../lib/make-badge-url'
import generateAllMarkup from '../lib/generate-image-markup'
import { advertisedStyles } from '../../supported-features.json'
import { Snippet } from './snippet'
import { BaseFont, H3, Badge, BlockInput } from './common'
const nonBreakingSpace = '\u00a0'
const ContentContainer = styled(BaseFont)`
text-align: center;
`
const WeeSnippet = ({ snippet, truncate = false }) => (
<Snippet truncate={truncate} fontSize="10pt" snippet={snippet} />
)
WeeSnippet.propTypes = {
snippet: PropTypes.string.isRequired,
truncate: PropTypes.bool,
}
export default class MarkupModal extends React.Component {
static propTypes = {
@@ -100,13 +112,13 @@ export default class MarkupModal extends React.Component {
renderLivePreview() {
const { badgeUrl } = this.state
const includesPlaceholders = badgeUrl.includes(':')
if (includesPlaceholders) {
return nonBreakingSpace
let src
if (badgeUrl && !includesPlaceholders) {
src = this.generateBuiltBadgeUrl()
} else {
const livePreviewUrl = this.generateBuiltBadgeUrl()
return <img className="badge-img" src={livePreviewUrl} />
src = undefined
}
return <Badge display="block" src={src} />
}
renderMarkup() {
@@ -128,31 +140,19 @@ export default class MarkupModal extends React.Component {
<div>
<p>
URL&nbsp;
<ClickToSelect>
<input className="code clickable" readOnly value={builtBadgeUrl} />
</ClickToSelect>
<WeeSnippet snippet={builtBadgeUrl} />
</p>
<p>
Markdown&nbsp;
<ClickToSelect>
<input className="code clickable" readOnly value={markdown} />
</ClickToSelect>
<WeeSnippet truncate snippet={markdown} />
</p>
<p>
reStructuredText&nbsp;
<ClickToSelect>
<input
className="code clickable"
readOnly
value={reStructuredText}
/>
</ClickToSelect>
<WeeSnippet truncate snippet={reStructuredText} />
</p>
<p>
AsciiDoc&nbsp;
<ClickToSelect>
<input className="code clickable" readOnly value={asciiDoc} />
</ClickToSelect>
<WeeSnippet truncate snippet={asciiDoc} />
</p>
</div>
)
@@ -173,7 +173,7 @@ export default class MarkupModal extends React.Component {
render() {
const { isOpen } = this
const { onRequestClose } = this.props
const { onRequestClose, example: { title } = {} } = this.props
const { link, badgeUrl, exampleUrl, style } = this.state
const common = {
@@ -190,62 +190,63 @@ export default class MarkupModal extends React.Component {
contentLabel="Example Modal"
ariaHideApp={false}
>
<form action="">
<p>{isOpen && this.renderLivePreview()}</p>
<p>
<label>
Link&nbsp;
<input
type="url"
value={link}
onChange={event => {
this.setState({ link: event.target.value })
}}
{...common}
/>
</label>
</p>
<p>
<label>
Path&nbsp;
<input
type="url"
value={badgeUrl}
onChange={event => {
this.setState({ badgeUrl: event.target.value })
}}
{...common}
/>
</label>
</p>
{exampleUrl && (
<ContentContainer>
<form action="">
<H3>{title}</H3>
{isOpen && this.renderLivePreview()}
<p>
Example&nbsp;
<ClickToSelect>
<input className="code clickable" readOnly value={exampleUrl} />
</ClickToSelect>
<label>
Link&nbsp;
<BlockInput
type="url"
value={link}
onChange={event => {
this.setState({ link: event.target.value })
}}
{...common}
/>
</label>
</p>
)}
<p>
<label>
Style&nbsp;
<select
value={style}
onChange={event => {
this.setState({ style: event.target.value })
}}
>
{advertisedStyles.map(style => (
<option key={style} value={style}>
{style}
</option>
))}
</select>
</label>
</p>
{isOpen && this.renderMarkup()}
{isOpen && this.renderDocumentation()}
</form>
<p>
<label>
Path&nbsp;
<BlockInput
type="url"
value={badgeUrl}
onChange={event => {
this.setState({ badgeUrl: event.target.value })
}}
{...common}
/>
</label>
</p>
{exampleUrl && (
<p>
Example&nbsp;
<Snippet fontSize="10pt" snippet={exampleUrl} />
</p>
)}
<p>
<label>
Style&nbsp;
<select
value={style}
onChange={event => {
this.setState({ style: event.target.value })
}}
>
{advertisedStyles.map(style => (
<option key={style} value={style}>
{style}
</option>
))}
</select>
</label>
</p>
{isOpen && this.renderMarkup()}
{isOpen && this.renderDocumentation()}
</form>
</ContentContainer>
</Modal>
)
}

View File

@@ -12,7 +12,6 @@ export default () => (
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="description" content={description} />
<link rel="icon" type="image/png" href="favicon.png" />
<link href="/static/main.css" rel="stylesheet" />
<link
href="https://fonts.googleapis.com/css?family=Lekton"
rel="stylesheet"

View File

@@ -0,0 +1,45 @@
import React from 'react'
import PropTypes from 'prop-types'
import ClickToSelect from '@mapbox/react-click-to-select'
import styled, { css } from 'styled-components'
const CodeContainer = styled.span`
vertical-align: middle;
display: inline-block;
${({ truncate }) =>
truncate &&
css`
max-width: 40%;
overflow: hidden;
text-overflow: ellipsis;
`};
`
const StyledCode = styled.code`
line-height: 1.2em;
padding: 0.1em 0.3em;
border-radius: 4px;
background: #eef;
font-family: Lekton;
font-size: ${({ fontSize }) => fontSize};
white-space: nowrap;
`
const Snippet = ({ snippet, truncate = false, fontSize }) => (
<CodeContainer truncate={truncate}>
<ClickToSelect>
<StyledCode fontSize={fontSize}>{snippet}</StyledCode>
</ClickToSelect>
</CodeContainer>
)
Snippet.propTypes = {
snippet: PropTypes.string.isRequired,
truncate: PropTypes.bool,
fontSize: PropTypes.string,
}
export { Snippet, StyledCode }

View File

@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { staticBadgeUrl } from '../lib/badge-url'
import { InlineInput } from './common'
export default class StaticBadgeMaker extends React.Component {
static propTypes = {
@@ -31,28 +32,22 @@ export default class StaticBadgeMaker extends React.Component {
render() {
return (
<form onSubmit={e => this.handleSubmit(e)}>
<input
className="short"
<InlineInput
value={this.state.subject}
onChange={event => this.setState({ subject: event.target.value })}
placeholder="subject"
/>{' '}
{}
<input
className="short"
/>
<InlineInput
value={this.state.status}
onChange={event => this.setState({ status: event.target.value })}
placeholder="status"
/>{' '}
{}
<input
className="short"
/>
<InlineInput
value={this.state.color}
onChange={event => this.setState({ color: event.target.value })}
list="default-colors"
placeholder="color"
/>{' '}
{}
/>
<datalist id="default-colors">
<option value="brightgreen" />
<option value="green" />
@@ -62,8 +57,7 @@ export default class StaticBadgeMaker extends React.Component {
<option value="red" />
<option value="lightgrey" />
<option value="blue" />
</datalist>{' '}
{}
</datalist>
<button>Make Badge</button>
</form>
)

View File

@@ -2,8 +2,9 @@ import React from 'react'
import PropTypes from 'prop-types'
import fetchPonyfill from 'fetch-ponyfill'
import debounce from 'lodash.debounce'
import BadgeExamples from './badge-examples'
import resolveUrl from '../lib/resolve-url'
import BadgeExamples from './badge-examples'
import { BlockInput } from './common'
export default class SuggestionAndSearch extends React.Component {
static propTypes = {
@@ -93,7 +94,7 @@ export default class SuggestionAndSearch extends React.Component {
return (
<section>
<form action="javascript:void 0" autoComplete="off">
<input
<BlockInput
onChange={event => this.queryChanged(event.target.value)}
autofill="off"
autoFocus

View File

@@ -1,9 +1,66 @@
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import StaticBadgeMaker from './static-badge-maker'
import DynamicBadgeMaker from './dynamic-badge-maker'
import styled from 'styled-components'
import { staticBadgeUrl } from '../lib/badge-url'
import { advertisedStyles, logos } from '../../supported-features.json'
import StaticBadgeMaker from './static-badge-maker'
import DynamicBadgeMaker from './dynamic-badge-maker'
import { H2, H3, Badge, VerticalSpace } from './common'
import { Snippet, StyledCode } from './snippet'
const LogoName = styled.span`
white-space: nowrap;
`
const Lhs = styled.td`
text-align: right;
`
const EscapingRuleTable = styled.table`
margin: auto;
`
const QueryParamTable = styled.table`
min-width: 50%;
margin: auto;
table-layout: fixed;
border-spacing: 20px 10px;
`
const QueryParamSyntax = styled.td`
max-width: 300px;
text-align: left;
`
const QueryParamDocumentation = styled.td`
max-width: 600px;
text-align: left;
`
const QueryParam = ({ snippet, documentation }) => (
<tr>
<QueryParamSyntax>
<Snippet snippet={snippet} />
</QueryParamSyntax>
<QueryParamDocumentation>{documentation}</QueryParamDocumentation>
</tr>
)
QueryParam.propTypes = {
snippet: PropTypes.string.isRequired,
documentation: PropTypes.element.isRequired,
}
const EscapingConversion = ({ lhs, rhs }) => (
<tr>
<Lhs>{lhs}</Lhs>
<td></td>
<td>{rhs}</td>
</tr>
)
EscapingConversion.propTypes = {
lhs: PropTypes.element.isRequired,
rhs: PropTypes.element.isRequired,
}
export default class Usage extends React.PureComponent {
static propTypes = {
@@ -27,12 +84,10 @@ export default class Usage extends React.PureComponent {
<p>
{colors.map((color, i) => (
<Fragment key={i}>
<img
className="badge-img"
<Badge
src={staticBadgeUrl(baseUrl, 'color', color, color)}
alt={color}
/>{' '}
{}
/>
</Fragment>
))}
</p>
@@ -42,98 +97,108 @@ export default class Usage extends React.PureComponent {
renderStyleExamples() {
const { baseUrl } = this.props
return (
<table className="badge-img">
<QueryParamTable>
<tbody>
{advertisedStyles.map((style, i) => {
{advertisedStyles.map(style => {
const snippet = `?style=${style}&logo=appveyor`
const badgeUrl = staticBadgeUrl(baseUrl, 'style', style, 'green', {
logo: 'appveyor',
style,
})
return (
<tr key={i}>
<td>
<img className="badge-img" src={badgeUrl} alt={style} />
</td>
<td>
<code>{badgeUrl}</code>
</td>
</tr>
<QueryParam
key={style}
snippet={snippet}
documentation={<Badge src={badgeUrl} alt={style} />}
/>
)
})}
</tbody>
</table>
</QueryParamTable>
)
}
static renderNamedLogos() {
const renderLogo = logo => (
<span className="nowrap" key={logo}>
{logo}
</span>
)
const renderLogo = logo => <LogoName key={logo}>{logo}</LogoName>
const [first, ...rest] = logos
return [renderLogo(first)].concat(
rest.reduce((result, logo) => result.concat([', ', renderLogo(logo)]), [])
)
}
static renderStaticBadgeEscapingRules() {
return (
<EscapingRuleTable>
<tbody>
<EscapingConversion
key="dashes"
lhs={
<span>
Dashes <code>--</code>
</span>
}
rhs={
<span>
<code>-</code> Dash
</span>
}
/>
<EscapingConversion
key="underscores"
lhs={
<span>
Underscores <code>__</code>
</span>
}
rhs={
<span>
<code>_</code> Underscore
</span>
}
/>
<EscapingConversion
key="spaces"
lhs={
<span>
<code>_</code> or Space <code>&nbsp;</code>
</span>
}
rhs={
<span>
<code>&nbsp;</code> Space
</span>
}
/>
</tbody>
</EscapingRuleTable>
)
}
render() {
const { baseUrl } = this.props
return (
<section>
<h2 id="your-badge">Your Badge</h2>
<H2 id="your-badge">Your Badge</H2>
<h3 id="static-badge">Static</h3>
<H3 id="static-badge">Static</H3>
<StaticBadgeMaker baseUrl={baseUrl} />
<hr className="spacing" />
<VerticalSpace />
<p>
<code>
{baseUrl}
/badge/&lt;SUBJECT&gt;-&lt;STATUS&gt;-&lt;COLOR&gt;.svg
</code>
<Snippet
snippet={`${baseUrl}/badge/<SUBJECT>-<STATUS>-<COLOR>.svg`}
/>
</p>
<table className="centered">
<tbody>
<tr>
<td>
Dashes <code>--</code>
</td>
<td></td>
<td>
<code>-</code> Dash
</td>
</tr>
<tr>
<td>
Underscores <code>__</code>
</td>
<td></td>
<td>
<code>_</code> Underscore
</td>
</tr>
<tr>
<td>
<code>_</code> or Space <code>&nbsp;</code>
</td>
<td></td>
<td>
<code>&nbsp;</code> Space
</td>
</tr>
</tbody>
</table>
{this.constructor.renderStaticBadgeEscapingRules()}
{this.renderColorExamples()}
<h3 id="dynamic-badge">Dynamic</h3>
<H3 id="dynamic-badge">Dynamic</H3>
<DynamicBadgeMaker baseUrl={baseUrl} />
<p>
<code>
<StyledCode>
{baseUrl}
/badge/dynamic/json.svg?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
<a
@@ -144,10 +209,10 @@ export default class Usage extends React.PureComponent {
$.DATA.SUBDATA
</a>
&gt;&amp;colorB=&lt;COLOR&gt;&amp;prefix=&lt;PREFIX&gt;&amp;suffix=&lt;SUFFIX&gt;
</code>
</StyledCode>
</p>
<p>
<code>
<StyledCode>
{baseUrl}
/badge/dynamic/xml.svg?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
<a
@@ -158,10 +223,10 @@ export default class Usage extends React.PureComponent {
//data/subdata
</a>
&gt;&amp;colorB=&lt;COLOR&gt;&amp;prefix=&lt;PREFIX&gt;&amp;suffix=&lt;SUFFIX&gt;
</code>
</StyledCode>
</p>
<p>
<code>
<StyledCode>
{baseUrl}
/badge/dynamic/yaml.svg?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
<a
@@ -172,12 +237,12 @@ export default class Usage extends React.PureComponent {
$.DATA.SUBDATA
</a>
&gt;&amp;colorB=&lt;COLOR&gt;&amp;prefix=&lt;PREFIX&gt;&amp;suffix=&lt;SUFFIX&gt;
</code>
</StyledCode>
</p>
<hr className="spacing" />
<VerticalSpace />
<h2 id="styles">Styles</h2>
<H2 id="styles">Styles</H2>
<p>
The following styles are available. Flat is the default. Examples are
@@ -189,92 +254,101 @@ export default class Usage extends React.PureComponent {
Here are a few other parameters you can use: (connecting several with
"&" is possible)
</p>
<table className="usage">
<QueryParamTable>
<tbody>
<tr>
<td>
<code>?label=healthinesses</code>
</td>
<td>
Override the default left-hand-side text (
<a href="https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding">
URL-Encoding
</a>
{} needed for spaces or special characters!)
</td>
</tr>
<tr>
<td>
<code>?logo=appveyor</code>
</td>
<td>
Insert one of the named logos from (
{this.constructor.renderNamedLogos()}) or{' '}
<a href="https://simpleicons.org/" target="_BLANK">
simple-icons
</a>
</td>
</tr>
<tr>
<td>
<code>?logo=data:image/png;base64,</code>
</td>
<td>Insert custom logo image ( 14px high)</td>
</tr>
<tr>
<td>
<code>?logoColor=violet</code>
</td>
<td>
Set the color of the logo (hex, rgb, rgba, hsl, hsla and css
named colors supported)
</td>
</tr>
<tr>
<td>
<code>?logoWidth=40</code>
</td>
<td>Set the horizontal space to give to the logo</td>
</tr>
<tr>
<td>
<code>?link=http://left&amp;link=http://right</code>
</td>
<td>
Specify what clicking on the left/right of a badge should do
(esp. for social badge style)
</td>
</tr>
<tr>
<td>
<code>?colorA=abcdef</code>
</td>
<td>
Set background of the left part (hex, rgb, rgba, hsl, hsla and
css named colors supported)
</td>
</tr>
<tr>
<td>
<code>?colorB=fedcba</code>
</td>
<td>
Set background of the right part (hex, rgb, rgba, hsl, hsla and
css named colors supported)
</td>
</tr>
<tr>
<td>
<code>?maxAge=3600</code>
</td>
<td>
Set the HTTP cache lifetime in secs (rules are applied to infer
a default value on a per-badge basis, any values specified below
the default will be ignored)
</td>
</tr>
<QueryParam
key="label"
snippet="?label=healthinesses"
documentation={
<span>
Override the default left-hand-side text (
<a href="https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding">
URL-Encoding
</a>
{} needed for spaces or special characters!)
</span>
}
/>
<QueryParam
key="logo"
snippet="?logo=appveyor"
documentation={
<span>
Insert one of the named logos from (
{this.constructor.renderNamedLogos()}) or{' '}
<a href="https://simpleicons.org/" target="_BLANK">
simple-icons
</a>
</span>
}
/>
<QueryParam
key="logoSvg"
snippet="?logo=data:image/png;base64,…"
documentation={
<span>Insert custom logo image ( 14px high)</span>
}
/>
<QueryParam
key="logoColor"
snippet="?logoColor=violet"
documentation={
<span>
Set the color of the logo (hex, rgb, rgba, hsl, hsla and css
named colors supported)
</span>
}
/>
<QueryParam
key="logoWidth"
snippet="?logoWidth=40"
documentation={
<span>Set the horizontal space to give to the logo</span>
}
/>
<QueryParam
key="link"
snippet="?link=http://left&amp;link=http://right"
documentation={
<span>
Specify what clicking on the left/right of a badge should do
(esp. for social badge style)
</span>
}
/>
<QueryParam
key="colorA"
snippet="?colorA=abcdef"
documentation={
<span>
Set background of the left part (hex, rgb, rgba, hsl, hsla and
css named colors supported)
</span>
}
/>
<QueryParam
key="colorB"
snippet="?colorB=fedcba"
documentation={
<span>
Set background of the right part (hex, rgb, rgba, hsl, hsla
and css named colors supported)
</span>
}
/>
<QueryParam
key="maxAge"
snippet="?maxAge=3600"
documentation={
<span>
Set the HTTP cache lifetime in secs (rules are applied to
infer a default value on a per-badge basis, any values
specified below the default will be ignored)
</span>
}
/>
</tbody>
</table>
</QueryParamTable>
<p>
We support <code>.svg</code>, <code>.json</code>, <code>.png</code>{' '}

108
package-lock.json generated
View File

@@ -1213,6 +1213,27 @@
"lazy-ass": "1.6.0"
}
},
"@emotion/is-prop-valid": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.6.8.tgz",
"integrity": "sha512-IMSL7ekYhmFlILXcouA6ket3vV7u9BqStlXzbKOF9HBtpUPMMlHU+bBxrLOa2NvleVwNIxeq/zL8LafLbeUXcA==",
"dev": true,
"requires": {
"@emotion/memoize": "^0.6.6"
}
},
"@emotion/memoize": {
"version": "0.6.6",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz",
"integrity": "sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ==",
"dev": true
},
"@emotion/unitless": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.3.tgz",
"integrity": "sha512-4zAPlpDEh2VwXswwr/t8xGNDGg8RQiPxtxZ3qQEXyQsBV39ptTdESCjuBvGze1nLMVrxmTIKmnO/nAV8Tqjjzg==",
"dev": true
},
"@iamstarkov/listr-update-renderer": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@iamstarkov/listr-update-renderer/-/listr-update-renderer-0.4.1.tgz",
@@ -1865,6 +1886,17 @@
"integrity": "sha1-Lk57RJa5OmVKHIAEInbeTk7rIOM=",
"dev": true
},
"babel-plugin-styled-components": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.9.2.tgz",
"integrity": "sha512-McnheW8RkBkur/mQw7rEwQO/oUUruQ/nIIj5LIRpsVL8pzG1oo1Y53xyvAYeOfamIrl4/ta7g1G/kuTR1ekO3A==",
"dev": true,
"requires": {
"@babel/helper-annotate-as-pure": "^7.0.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"lodash": "^4.17.10"
}
},
"babel-plugin-syntax-jsx": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
@@ -3445,6 +3477,12 @@
"randomfill": "^1.0.3"
}
},
"css-color-keywords": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
"integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=",
"dev": true
},
"css-select": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
@@ -3461,6 +3499,17 @@
"resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.0.tgz",
"integrity": "sha1-AQKz0UYw34bD65+p9UVicBBs+ZA="
},
"css-to-react-native": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.2.2.tgz",
"integrity": "sha512-w99Fzop1FO8XKm0VpbQp3y5mnTnaS+rtCvS+ylSEOK76YXO5zoHQx/QMB1N54Cp+Ya9jB9922EHrh14ld4xmmw==",
"dev": true,
"requires": {
"css-color-keywords": "^1.0.0",
"fbjs": "^0.8.5",
"postcss-value-parser": "^3.3.0"
}
},
"css-tree": {
"version": "1.0.0-alpha.28",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz",
@@ -8754,6 +8803,12 @@
"integrity": "sha512-D2JKK2DTuVYQqquBWco3K6UfSVyVwmd58dgNqh+TgxHOZdTmR8I130gjMbVCkemDl/EzqDA62417cJxKL3/FFA==",
"dev": true
},
"memoize-one": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.3.tgz",
"integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==",
"dev": true
},
"memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@@ -12667,6 +12722,12 @@
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
"dev": true
},
"postcss-value-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
"dev": true
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@@ -14948,6 +15009,53 @@
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"dev": true
},
"styled-components": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.1.2.tgz",
"integrity": "sha512-NdvWatJ2WLqZxAvto+oH0k7GAC/TlAUJTrHoXJddjbCrU6U23EmVbb9LXJBF+d6q6hH+g9nQYOWYPUeX/Vlc2w==",
"dev": true,
"requires": {
"@emotion/is-prop-valid": "^0.6.8",
"@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"
},
"dependencies": {
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"stylis": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz",
"integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==",
"dev": true
},
"stylis-rule-sheet": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz",
"integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"styled-jsx": {
"version": "2.2.6",
"resolved": "http://registry.npmjs.org/styled-jsx/-/styled-jsx-2.2.6.tgz",

View File

@@ -181,6 +181,7 @@
"sinon": "^7.2.2",
"sinon-chai": "^3.3.0",
"snap-shot-it": "^6.2.7",
"styled-components": "^4.1.2",
"tmp": "0.0.33",
"url": "^0.11.0"
},

31
pages/_document.js Normal file
View File

@@ -0,0 +1,31 @@
// https://www.styled-components.com/docs/advanced#nextjs
import Document, { Head, Main, NextScript } from 'next/document'
import React from 'react'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static getInitialProps({ renderPage }) {
const sheet = new ServerStyleSheet()
const page = renderPage(App => props =>
sheet.collectStyles(<App {...props} />)
)
const styleTags = sheet.getStyleElement()
return { ...page, styleTags }
}
render() {
return (
<html>
<Head>
<title>My page</title>
{this.props.styleTags}
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}

View File

@@ -1,122 +0,0 @@
html {
background-attachment: fixed;
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDAiIGhlaWdodD0iNDgwIj48ZmlsdGVyIGlkPSJhIj48ZmVUdXJidWxlbmNlIGJhc2VGcmVxdWVuY3k9Ii4wOCIgbnVtT2N0YXZlcz0iOCIgc3RpdGNoVGlsZXM9InN0aXRjaCIgc2VlZD0iMzQ2Ii8+PGZlQ29sb3JNYXRyaXggdmFsdWVzPSIxIDAgMCAwIDAgIDEgMCAwIDAgMCAgMSAwIDAgMCAuOSAgLjAxIDAgMCAwIC4wMSIvPjwvZmlsdGVyPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbHRlcj0idXJsKCNhKSIvPjwvc3ZnPg==);
}
:root,
dialog {
text-align: center;
font-family: Lekton, sans-serif;
color: #534;
}
code,
.code {
font-family: Lekton;
white-space: pre-wrap;
padding: 0 4px;
background: #eef;
border-radius: 4px;
}
input.short {
width: 5em;
}
input {
text-align: center;
border: solid #b9a;
color: #534;
border-width: 0 0 1px 0;
width: 40%;
height: 15px;
padding: 0;
background-color: transparent;
}
input:focus {
outline: 0;
}
hr {
width: 40%;
border-width: 1px 0 0 0;
}
ul {
text-align: left;
margin-left: 25%;
}
table {
min-width: 50%;
margin: auto;
}
table.centered > tbody > tr > td:first-child {
text-align: right;
}
table.usage {
table-layout: fixed;
border-spacing: 20px 10px;
}
.nowrap {
white-space: nowrap;
}
table.usage td:first-of-type {
max-width: 300px;
}
table.usage td:nth-of-type(2) {
max-width: 600px;
}
th,
td {
text-align: left;
}
h2,
h3 {
font-style: italic;
}
h2::before {
content: '☙ ';
}
h2::after {
content: ' ❧';
}
h2 {
margin-top: 12mm;
font-variant: small-caps;
}
hr.spacing {
border: 0;
display: block;
height: 3mm;
}
.clickable {
cursor: pointer;
}
.excluded {
display: none;
}
.badge-img {
min-height: 20px;
vertical-align: middle;
}
.spaced-row * {
margin-left: 10px;
margin-right: 10px;
}