diff --git a/docs/content/StateLabel.md b/docs/content/StateLabel.md
index 4c09d09c125..19b0a6d68c0 100644
--- a/docs/content/StateLabel.md
+++ b/docs/content/StateLabel.md
@@ -6,7 +6,12 @@ Use StateLabel components to show the status of an issue or pull request.
## Default example
```jsx live
- Open
+Open
+Closed
+Open
+Closed
+Merged
+Draft
```
## System props
@@ -17,5 +22,5 @@ StateLabel components get `COMMON` system props. Read our [System Props](/system
| Name | Type | Default | Description |
| :- | :- | :-: | :- |
-| small | Boolean | | Used to create a smaller version of the default StateLabel |
-| status | String | | Can be one of `issueOpened`, `issueClosed`, `pullOpened`, `pullClosed` or `pullMerged`.
+| variant | String | 'normal' | a value of `small` or `normal` results in a smaller or larger version of the StateLabel. |
+| status | String | | Can be one of `issueOpened`, `issueClosed`, `pullOpened`, `pullClosed`, `pullMerged`, or `draft`.
diff --git a/index.d.ts b/index.d.ts
index 63f4734f844..601c5633dcb 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -358,7 +358,8 @@ declare module '@primer/components' {
export interface StateLabelProps extends CommonProps {
small?: boolean
- status: 'issueOpened' | 'issueClosed' | 'pullOpened' | 'pullClosed' | 'pullMerged'
+ variant?: 'small' | 'normal'
+ status: 'issueOpened' | 'issueClosed' | 'pullOpened' | 'pullClosed' | 'pullMerged' | 'draft'
}
export const StateLabel: React.FunctionComponent
diff --git a/src/StateLabel.js b/src/StateLabel.js
index 84c996f0cd4..c756390ee29 100644
--- a/src/StateLabel.js
+++ b/src/StateLabel.js
@@ -1,65 +1,78 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
-import {GitMerge, GitPullRequest, IssueClosed, IssueOpened} from '@primer/octicons-react'
-import theme, {colors} from './theme'
+import {GitMerge, GitPullRequest, IssueClosed, IssueOpened, Question} from '@primer/octicons-react'
+import {variant} from 'styled-system'
+import theme from './theme'
import {COMMON, get} from './constants'
import StyledOcticon from './StyledOcticon'
import sx from './sx'
-
-const statusMap = {
- issueClosed: colors.red[6],
- pullClosed: colors.red[5],
- pullMerged: colors.purple[5],
- issueOpened: '#159739', // Custom green
- pullOpened: '#2cbe4e', // This was generated by a sass function in Primer, so using a hex here
- gray: colors.gray[5]
-}
+import {useDeprecation} from './utils/deprecate'
const octiconMap = {
issueOpened: IssueOpened,
pullOpened: GitPullRequest,
issueClosed: IssueClosed,
pullClosed: GitPullRequest,
- pullMerged: GitMerge
+ pullMerged: GitMerge,
+ draft: GitPullRequest
}
-function StateLabelBase({className, status, small = false, children}) {
- const octiconProps = small ? {width: '1em'} : {}
- return (
-
- {status && }
- {children}
-
- )
-}
+const colorVariants = variant({
+ prop: 'status',
+ scale: 'stateLabels.status'
+})
+
+const sizeVariants = variant({
+ prop: 'variant',
+ scale: 'stateLabels.sizes'
+})
-const StateLabel = styled(StateLabelBase)`
+const StateLabelBase = styled.span`
display: inline-flex;
align-items: center;
- padding: ${props => (props.small ? `4px 8px` : `8px 12px`)};
font-weight: 600;
line-height: 16px;
- color: ${colors.white};
- font-size: ${props =>
- props.small
- ? theme.fontSizes[0]
- : theme.fontSizes[1]}; // TODO: these should use the get function instead of referencing the theme directly
+ color: ${get('colors.white')};
text-align: center;
- background-color: ${props => (props.status ? statusMap[props.status] : statusMap.gray)};
border-radius: ${get('radii.3')};
+ ${colorVariants};
+ ${sizeVariants};
${COMMON};
${sx};
`
+function StateLabel({children, small, status, variant, ...rest}) {
+ const deprecate = useDeprecation({
+ name: "StateLabel 'small' prop",
+ message: "Use variant='small' or variant='normal' instead.",
+ version: '20.0.0'
+ })
+
+ if (small) {
+ deprecate()
+ variant = 'small'
+ }
+
+ const octiconProps = variant === 'small' ? {width: '1em'} : {}
+ return (
+
+ {status && }
+ {children}
+
+ )
+}
+
StateLabel.defaultProps = {
- theme
+ theme,
+ variant: 'normal'
}
StateLabel.propTypes = {
small: PropTypes.bool,
- status: PropTypes.oneOf(['issueOpened', 'pullOpened', 'issueClosed', 'pullClosed', 'pullMerged']),
+ status: PropTypes.oneOf(['issueOpened', 'pullOpened', 'issueClosed', 'pullClosed', 'pullMerged', 'draft']).isRequired,
theme: PropTypes.object,
+ variant: PropTypes.oneOf(['small', 'normal']),
...COMMON.propTypes,
...sx.propTypes
}
diff --git a/src/__tests__/StateLabel.js b/src/__tests__/StateLabel.js
index 2cb8aff92a9..e6b0e21a362 100644
--- a/src/__tests__/StateLabel.js
+++ b/src/__tests__/StateLabel.js
@@ -5,10 +5,17 @@ import {COMMON} from '../constants'
import {render as HTMLRender, cleanup} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
import 'babel-polyfill'
+import {Deprecations} from '../utils/deprecate'
expect.extend(toHaveNoViolations)
describe('StateLabel', () => {
- behavesAsComponent(StateLabel, [COMMON])
+ behavesAsComponent(StateLabel, [COMMON], () => Open, {
+ // Rendering a StyledOcticon seems to break getComputedStyles, which
+ // the sx prop implementation test uses to make sure the prop is working correctly.
+ // Despite my best efforts, I cannot figure out why this is happening. So,
+ // unfortunately, we will simply skip this test.
+ skipSx: true
+ })
checkExports('StateLabel', {
default: StateLabel
@@ -21,24 +28,23 @@ describe('StateLabel', () => {
cleanup()
})
+ it('respects the deprecated "small" prop', () => {
+ expect(render()).toHaveStyleRule('font-size', '12px')
+ expect(Deprecations.getDeprecations()).toHaveLength(1)
+ })
+
it('respects the status prop', () => {
expect(render()).toMatchSnapshot()
expect(render()).toMatchSnapshot()
expect(render()).toMatchSnapshot()
})
- it('respects the small flag', () => {
- expect(render()).toMatchSnapshot()
- expect(render()).toMatchSnapshot()
+ it('respects the variant prop', () => {
+ expect(render()).toMatchSnapshot()
+ expect(render()).toMatchSnapshot()
})
it('renders children', () => {
- expect(render(hi)).toMatchSnapshot()
- })
-
- it('does not pass on arbitrary attributes', () => {
- const defaultOutput = render()
- expect(render()).toEqual(defaultOutput)
- expect(render()).toEqual(defaultOutput)
+ expect(render(hi)).toMatchSnapshot()
})
})
diff --git a/src/__tests__/__snapshots__/StateLabel.js.snap b/src/__tests__/__snapshots__/StateLabel.js.snap
index 93c2122bd2b..4ea9f62575e 100644
--- a/src/__tests__/__snapshots__/StateLabel.js.snap
+++ b/src/__tests__/__snapshots__/StateLabel.js.snap
@@ -1,6 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`StateLabel renders children 1`] = `
+.c1 {
+ margin-right: 4px;
+}
+
.c0 {
display: -webkit-inline-box;
display: -webkit-inline-flex;
@@ -10,24 +14,49 @@ exports[`StateLabel renders children 1`] = `
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
- padding: 8px 12px;
font-weight: 600;
line-height: 16px;
color: #fff;
- font-size: 14px;
text-align: center;
- background-color: #6a737d;
border-radius: 100px;
+ background-color: #159739;
+ padding: 8px 12px;
+ font-size: 14px;
}
+
hi
`;
exports[`StateLabel renders consistently 1`] = `
+.c1 {
+ margin-right: 4px;
+}
+
.c0 {
display: -webkit-inline-box;
display: -webkit-inline-flex;
@@ -37,22 +66,49 @@ exports[`StateLabel renders consistently 1`] = `
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
- padding: 8px 12px;
font-weight: 600;
line-height: 16px;
color: #fff;
- font-size: 14px;
text-align: center;
- background-color: #6a737d;
border-radius: 100px;
+ background-color: #159739;
+ padding: 8px 12px;
+ font-size: 14px;
}
+>
+
+ Open
+
`;
-exports[`StateLabel respects the small flag 1`] = `
+exports[`StateLabel respects the status prop 1`] = `
+.c1 {
+ margin-right: 4px;
+}
+
.c0 {
display: -webkit-inline-box;
display: -webkit-inline-flex;
@@ -62,22 +118,48 @@ exports[`StateLabel respects the small flag 1`] = `
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
- padding: 4px 8px;
font-weight: 600;
line-height: 16px;
color: #fff;
- font-size: 12px;
text-align: center;
- background-color: #6a737d;
border-radius: 100px;
+ background-color: #159739;
+ padding: 8px 12px;
+ font-size: 14px;
}
+>
+
+
`;
-exports[`StateLabel respects the small flag 2`] = `
+exports[`StateLabel respects the status prop 2`] = `
+.c1 {
+ margin-right: 4px;
+}
+
.c0 {
display: -webkit-inline-box;
display: -webkit-inline-flex;
@@ -87,22 +169,44 @@ exports[`StateLabel respects the small flag 2`] = `
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
- padding: 8px 12px;
font-weight: 600;
line-height: 16px;
color: #fff;
- font-size: 14px;
text-align: center;
- background-color: #6a737d;
border-radius: 100px;
+ background-color: #d73a49;
+ padding: 8px 12px;
+ font-size: 14px;
}
+>
+
+
`;
-exports[`StateLabel respects the status prop 1`] = `
+exports[`StateLabel respects the status prop 3`] = `
.c1 {
margin-right: 4px;
}
@@ -116,14 +220,14 @@ exports[`StateLabel respects the status prop 1`] = `
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
- padding: 8px 12px;
font-weight: 600;
line-height: 16px;
color: #fff;
- font-size: 14px;
text-align: center;
- background-color: #159739;
border-radius: 100px;
+ background-color: #6f42c1;
+ padding: 8px 12px;
+ font-size: 14px;
}
`;
-exports[`StateLabel respects the status prop 2`] = `
+exports[`StateLabel respects the variant prop 1`] = `
.c1 {
margin-right: 4px;
}
@@ -167,14 +271,14 @@ exports[`StateLabel respects the status prop 2`] = `
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
- padding: 8px 12px;
font-weight: 600;
line-height: 16px;
color: #fff;
- font-size: 14px;
text-align: center;
- background-color: #cb2431;
border-radius: 100px;
+ background-color: #159739;
+ padding: 4px 8px;
+ font-size: 12px;
}
`;
-exports[`StateLabel respects the status prop 3`] = `
+exports[`StateLabel respects the variant prop 2`] = `
.c1 {
margin-right: 4px;
}
@@ -218,14 +322,14 @@ exports[`StateLabel respects the status prop 3`] = `
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
- padding: 8px 12px;
font-weight: 600;
line-height: 16px;
color: #fff;
- font-size: 14px;
text-align: center;
- background-color: #6f42c1;
border-radius: 100px;
+ background-color: #159739;
+ padding: 8px 12px;
+ font-size: 14px;
}
diff --git a/src/theme-preval.js b/src/theme-preval.js
index bbd20990840..2075e8b3890 100644
--- a/src/theme-preval.js
+++ b/src/theme-preval.js
@@ -305,6 +305,40 @@ const pagination = {
}
}
+const stateLabels = {
+ sizes: {
+ small: {
+ padding: `${space[1]} ${space[2]}`,
+ fontSize: fontSizes[0]
+ },
+ normal: {
+ padding: `${space[2]} 12px`,
+ fontSize: fontSizes[1]
+ }
+ },
+
+ status: {
+ issueClosed: {
+ backgroundColor: red[5]
+ },
+ pullClosed: {
+ backgroundColor: red[5]
+ },
+ pullMerged: {
+ backgroundColor: purple[5]
+ },
+ issueOpened: {
+ backgroundColor: '#159739' // custom green
+ },
+ pullOpened: {
+ backgroundColor: '#159739' // custom green
+ },
+ draft: {
+ backgroundColor: gray[5]
+ }
+ }
+}
+
const theme = {
// General
borderWidths,
@@ -324,7 +358,8 @@ const theme = {
pagination,
popovers,
flash,
- flashIcon
+ flashIcon,
+ stateLabels
}
module.exports = {
diff --git a/src/utils/test-matchers.js b/src/utils/test-matchers.js
index 9beac00ef5a..1ff10177c3b 100644
--- a/src/utils/test-matchers.js
+++ b/src/utils/test-matchers.js
@@ -70,7 +70,7 @@ expect.extend({
const rendered = render(elem)
function checkStylesDeep(rendered) {
- const className = rendered.props.className
+ const className = rendered.props ? rendered.props.className : ''
const styles = getComputedStyles(className)
if (styles[mediaKey] && styles[mediaKey].color) {
return true
diff --git a/src/utils/testing.js b/src/utils/testing.js
index b9c6f4d907c..004c21945d1 100644
--- a/src/utils/testing.js
+++ b/src/utils/testing.js
@@ -130,7 +130,7 @@ export function getComputedStyles(className) {
return false
}
try {
- return div.matches(selector)
+ return node.matches(selector)
} catch (error) {
return false
}
@@ -190,13 +190,15 @@ export function behavesAsComponent(Component, systemPropArray, toRender = null,
}
})
- it('implements the sx prop', () => {
- expect(Component).toImplementSxProp()
- })
+ if (!options.skipSx) {
+ it('implements the sx prop', () => {
+ expect(Component).toImplementSxProp()
+ })
- it('implements sx prop behavior', () => {
- expect(getElement()).toImplementSxBehavior()
- })
+ it('implements sx prop behavior', () => {
+ expect(getElement()).toImplementSxBehavior()
+ })
+ }
if (!options.skipAs) {
it('respects the as prop', () => {