Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DonutChart → Donut, DonutSlice → Donut.Slice #269

Merged
merged 16 commits into from
Sep 28, 2018
Merged
24 changes: 24 additions & 0 deletions pages/components/docs/Donut.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Donut

## Default example
```.jsx
<Donut mr={1} data={{error: 2, pending: 3, success: 5}} />
<Donut mr={1}>
<Donut.Slice value={1} state="pending" />
<Donut.Slice value={1} state="success" />
<Donut.Slice value={1} state="error" />
</Donut>
```

## System props

Donut components get `space` system props. Read our [System Props](/system-props) doc page for a full list of available props.
shawnbot marked this conversation as resolved.
Show resolved Hide resolved

## Component props

| Prop name | Type | Description |
shawnbot marked this conversation as resolved.
Show resolved Hide resolved
| :- | :- | :- |
| 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

export const meta = {displayName: 'Donut'}
24 changes: 0 additions & 24 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'])
shawnbot marked this conversation as resolved.
Show resolved Hide resolved
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