-
Notifications
You must be signed in to change notification settings - Fork 528
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
Add Caret component #58
Changes from all commits
27c075b
b37d625
17a0dc6
e441c8f
a68197e
805fbf5
633b648
508cf48
a5652e9
bfadd7d
139efd7
b168fd8
5a13341
6954385
79d076c
b561834
2f3d44c
408c942
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import React from 'react' | ||
import PropTypes from 'prop-types' | ||
import theme from './theme' | ||
|
||
const oppositeEdge = { | ||
top: 'Bottom', | ||
right: 'Left', | ||
bottom: 'Top', | ||
left: 'Right' | ||
} | ||
|
||
const perpendicularEdge = { | ||
top: 'Left', | ||
right: 'Top', | ||
bottom: 'Left', | ||
left: 'Top' | ||
} | ||
|
||
function getPosition({edge, align}) { | ||
const opposite = oppositeEdge[edge].toLowerCase() | ||
const perp = perpendicularEdge[edge].toLowerCase() | ||
return { | ||
[opposite]: '100%', | ||
[perp]: '50%' | ||
} | ||
} | ||
|
||
export default function Caret({css, ...rest}) { | ||
// TODO: should the svg switch even be configurable, | ||
// or do we use feature detection here? | ||
return css | ||
? <CaretCSS {...rest} /> | ||
: <CaretSVG {...rest} /> | ||
} | ||
|
||
Caret.defaultProps = { | ||
align: 'center', | ||
borderColor: theme.colors.gray[2], | ||
borderWidth: 1, | ||
edge: 'bottom', | ||
fill: theme.colors.white, | ||
position: 'absolute', | ||
size: 8, | ||
css: false | ||
} | ||
|
||
Caret.propTypes = { | ||
align: PropTypes.oneOf(['start', 'center', 'end']), | ||
borderColor: PropTypes.string, | ||
borderWidth: PropTypes.number, | ||
edge: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), | ||
fill: PropTypes.string, | ||
position: PropTypes.oneOf(['absolute', 'relative']), | ||
size: PropTypes.number, | ||
css: PropTypes.bool | ||
} | ||
|
||
function CaretCSS(props) { | ||
const { | ||
align, | ||
borderColor, | ||
borderWidth, | ||
edge, | ||
fill, | ||
position, | ||
size | ||
} = props | ||
|
||
const opposite = oppositeEdge[edge] | ||
const perp = perpendicularEdge[edge] | ||
|
||
const style = { | ||
pointerEvents: 'none', | ||
position, | ||
...getPosition({edge, align}) | ||
} | ||
|
||
const common = { | ||
borderStyle: 'solid', | ||
borderWidth, | ||
position: 'absolute', | ||
[opposite.toLowerCase()]: '100%', | ||
} | ||
|
||
const after = { | ||
...common, | ||
borderColor: 'transparent', | ||
borderWidth: size, | ||
[`border${opposite}Color`]: fill, | ||
[`margin${perp}`]: -size | ||
} | ||
|
||
const before = { | ||
...common, | ||
borderColor: 'transparent', | ||
[`border${opposite}Color`]: borderColor, | ||
borderWidth: borderWidth + size, | ||
[`margin${perp}`]: -(size + borderWidth) | ||
} | ||
|
||
return ( | ||
<div style={style}> | ||
<div style={before} /> | ||
<div style={after} /> | ||
</div> | ||
) | ||
} | ||
|
||
function CaretSVG(props) { | ||
const { | ||
align, | ||
borderColor, | ||
borderWidth, | ||
edge, | ||
fill, | ||
position, | ||
size | ||
} = props | ||
|
||
const perp = perpendicularEdge[edge] | ||
|
||
const style = { | ||
pointerEvents: 'none', | ||
position, | ||
...getPosition({edge, align}), | ||
[`margin${perp}`]: -size | ||
} | ||
|
||
// note: these arrays represent points in the form [x, y] | ||
const a = [-size, 0] | ||
const b = [0, size] | ||
const c = [size, 0] | ||
|
||
// spaces are optional in path `d` attribute, and points are | ||
// represented in the form `x,y` -- which is what the arrays above | ||
// become when stringified! | ||
const triangle = `M${a}L${b}L${c}L${a}Z` | ||
const line = `M${a}L${b}L${c}` | ||
|
||
const transform = { | ||
top: `translate(${[size, size * 2]}) rotate(180)`, | ||
right: `translate(${[0, size]}) rotate(-90)`, | ||
bottom: `translate(${[size, 0]})`, | ||
left: `translate(${[size * 2, size]}) rotate(90)` | ||
}[edge] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought that this was easier to read than a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's fine this way! |
||
|
||
return ( | ||
<svg width={size * 2} height={size * 2} style={style}> | ||
<g transform={transform}> | ||
<path d={triangle} fill={fill} /> | ||
<path d={line} | ||
fill='none' | ||
stroke={borderColor} | ||
stroke-width={borderWidth} /> | ||
</g> | ||
</svg> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import React from 'react' | ||
import Caret from '../Caret' | ||
import {render} from '../utils/testing' | ||
|
||
describe('Caret', () => { | ||
it('outputs <svg> by default', () => { | ||
expect(render(<Caret />).type).toEqual('svg') | ||
}) | ||
|
||
it('outputs a <div> if the "css" prop is present', () => { | ||
expect(render(<Caret css />).type).toEqual('div') | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like something we might want a helper for in
props.js
because it's kind of a pain to maintain. I'd like to see something like:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I was running into this when I was working on the Dropdown stuff