-
Notifications
You must be signed in to change notification settings - Fork 538
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #269 from primer/child-components
DonutChart → Donut, DonutSlice → Donut.Slice
- Loading branch information
Showing
12 changed files
with
215 additions
and
207 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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']) |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | ||
}) | ||
}) | ||
}) |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.