Skip to content

Commit

Permalink
Merge pull request #269 from primer/child-components
Browse files Browse the repository at this point in the history
DonutChart → Donut, DonutSlice → Donut.Slice
  • Loading branch information
Emily authored Sep 28, 2018
2 parents 0aee35d + a487638 commit 027b377
Show file tree
Hide file tree
Showing 12 changed files with 215 additions and 207 deletions.
43 changes: 43 additions & 0 deletions pages/components/docs/Donut.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Donut
The Donut component is a circular chart that shows the relative share of commit status states for a pull request.

## The `data` prop
The `data` prop is the simplest way to define the share of states. It takes an object literal with states as keys and the number of statuses with that state as values. Slices are always rendered clockwise in descending order by size.

```.jsx
<Donut data={{error: 2, pending: 3, success: 5}} />
```

When using the `data` prop, the fill of each slice comes from the corresponding value in the theme's `colors.state` object. In other words, if `theme.colors.state.error = "red"`, then the `error` slice will get `fill="red"`. You can customize the slice colors by either passing a custom `theme` prop or using the `Donut.Slice` component described below.

## System props

Donut components get `space` system props. Read our [System Props](/system-props) doc page for a full list of available props.

## Component props

| Prop name | Type | Description |
| :- | :- | :- |
| data | Object | Use the keys `error`, `pending`, and `success` to set values used to generate slices in the chart |
| size | Number | Used to set the width and height of the component |

# Donut.Slice
If you need to customize the color of your slices, you can use the `Donut.Slice` component as a child of `Donut`.

```.jsx
<Donut>
<Donut.Slice value={1} fill="pink" />
<Donut.Slice value={1} fill="salmon" />
<Donut.Slice value={1} fill="tomato" />
</Donut>
```

## `Donut.Slice` component props

| Prop name | Type | Description |
| :- | :- | :- |
| state | String | The commit status state which this slice represents |
| value | Number | The number of statuses with this slice's state |
| fill | String | The fill color of the slice, which overrides the color determined by the `state` prop |

export const meta = {displayName: 'Donut'}
26 changes: 0 additions & 26 deletions pages/components/docs/DonutChart.md

This file was deleted.

2 changes: 1 addition & 1 deletion pages/components/docs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export {meta as CircleBadge} from './CircleBadge.md'
export {meta as CircleOcticon} from './CircleOcticon.md'
export {meta as CounterLabel} from './CounterLabel.md'
export {meta as Details} from './Details.md'
export {meta as DonutChart} from './DonutChart.md'
export {meta as Donut} from './Donut.md'
export {meta as Dropdown} from './Dropdown.md'
export {meta as FilterList} from './FilterList.md'
export {meta as Flash} from './Flash.md'
Expand Down
80 changes: 80 additions & 0 deletions src/Donut.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react'
import PropTypes from 'prop-types'
import {arc as Arc, pie as Pie} from 'd3-shape'
import {themeGet} from 'styled-system'
import {withSystemProps, withDefaultTheme} from './system-props'

const defaultColor = '#666'
const getStateColors = themeGet('colors.state', {})

function Donut(props) {
const {className, data, children = mapData(data), size} = props

const radius = size / 2
const innerRadius = radius - 6

const pie = Pie().value(child => child.props.value)

// coerce the children into an array
const childList = React.Children.toArray(children)
const arcData = pie(childList)
const arc = Arc()
.innerRadius(innerRadius)
.outerRadius(radius)

const slices = childList.map((child, i) => {
return React.cloneElement(child, {d: arc(arcData[i])})
})

return (
<svg width={size} height={size} className={className}>
<g transform={`translate(${radius},${radius})`}>{slices}</g>
</svg>
)
}

function mapData(data) {
return Object.keys(data).map(key => <Slice key={key} state={key} value={data[key]} />)
}

Donut.defaultProps = {
size: 30
}

Donut.propTypes = {
// require elements, not mixed content: <Slice>, <title>, etc.
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
data: PropTypes.objectOf(PropTypes.number),
size: PropTypes.number
}

const Slice = withDefaultTheme(props => {
const {children, d, fill, state, value} = props
const stateColors = getStateColors(props)
const color = fill || stateColors[state] || stateColors.unknown || defaultColor
return (
<path d={d} fill={color} data-value={value}>
{children}
</path>
)
})

Slice.propTypes = {
// <title> is really the only thing that should be acceptable here
children: PropTypes.shape({type: 'title'}),
d: PropTypes.string,
fill: PropTypes.string,
state: PropTypes.string,
/* eslint-disable react/no-unused-prop-types */
theme: PropTypes.shape({
colors: PropTypes.shape({
state: PropTypes.objectOf(PropTypes.string)
})
}),
/* eslint-enable */
value: PropTypes.number
}

Donut.Slice = Slice

export default withSystemProps(Donut, ['space'])
48 changes: 0 additions & 48 deletions src/DonutChart.js

This file was deleted.

36 changes: 0 additions & 36 deletions src/DonutSlice.js

This file was deleted.

85 changes: 85 additions & 0 deletions src/__tests__/Donut.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react'
import Donut from '../Donut'
import theme, {colors} from '../theme'
import {render} from '../utils/testing'

const {state} = colors

describe('Donut', () => {
it('is a system component', () => {
expect(Donut.systemComponent).toEqual(true)
})

it('renders the data prop', () => {
const donut = render(<Donut data={{error: 1}} />)
expect(donut).toMatchSnapshot()

expect(donut.type).toEqual('svg')
expect(donut.props.width).toEqual(30)
expect(donut.props.height).toEqual(30)
expect(donut.children).toHaveLength(1)

const [g] = donut.children
expect(g.type).toEqual('g')
expect(g.children).toHaveLength(1)

const [slice] = g.children
expect(slice.type).toEqual('path')
// expect(slice.props.fill).toEqual(colors.state.error)
})

it('renders Donut.Slice children', () => {
const donut = render(
<Donut>
<Donut.Slice state="success" value={1} />
<Donut.Slice state="failure" value={1} />
</Donut>
)
expect(donut).toMatchSnapshot()
expect(donut.children).toHaveLength(1)
const slices = donut.children[0].children
expect(slices).toHaveLength(2)
expect(slices.map(slice => slice.type)).toEqual(['path', 'path'])
expect(slices[0].props.fill).toEqual(colors.state.success)
expect(slices[1].props.fill).toEqual(colors.state.failure)
})

it('renders a single Donut.Slice child', () => {
const donut = render(
<Donut>
<Donut.Slice state="success" value={1} />
</Donut>
)
expect(donut).toMatchSnapshot()
expect(donut.type).toEqual('svg')
})

it('respects margin utility prop', () => {
expect(render(<Donut m={4} data={{error: 1}} />)).toHaveStyleRule('margin', `${theme.space[4]}px`)
})

it('respects padding utility prop', () => {
expect(render(<Donut p={4} data={{error: 1}} />)).toHaveStyleRule('padding', `${theme.space[4]}px`)
})

describe('Donut.Slice', () => {
it('renders known states as colors', () => {
expect(render(<Donut.Slice state="error" />).props.fill).toEqual(state.error)
expect(render(<Donut.Slice state="pending" />).props.fill).toEqual(state.pending)
expect(render(<Donut.Slice state="success" />).props.fill).toEqual(state.success)
expect(render(<Donut.Slice state="unknown" />).props.fill).toEqual(state.unknown)
})

it('renders unknown states with theme.colors.state.unknown', () => {
expect(render(<Donut.Slice state="xyz" />).props.fill).toEqual(state.unknown)
})

it('renders the fallback color when no state color is found in the theme', () => {
expect(render(<Donut.Slice state="error" theme={{}} />).props.fill).toEqual('#666')
})

it('respects the fill attribute', () => {
expect(render(<Donut.Slice fill="pink" />).props.fill).toEqual('pink')
})
})
})
63 changes: 0 additions & 63 deletions src/__tests__/DonutChart.js

This file was deleted.

Loading

0 comments on commit 027b377

Please sign in to comment.