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

Feature/anxr 202 tooltip #247

Merged
merged 38 commits into from
May 19, 2016
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ddb1446
add 'right' and 'left' directions to ContextMenu
sodiumjoe May 9, 2016
fcc5e02
add right and left directions, and `alignment` prop
sodiumjoe May 11, 2016
fd38ac3
add alignment: center
sodiumjoe May 12, 2016
32be4e5
add directionOffset and alignmentOffset
sodiumjoe May 12, 2016
cae5f27
fix directionOffset direction for direcion: down
sodiumjoe May 12, 2016
3cc04b1
improve ToolTip examples
sodiumjoe May 13, 2016
bc1864b
add getAlignmentOffset prop
sodiumjoe May 13, 2016
9ec16e5
position and style ToolTip
sodiumjoe May 13, 2016
a601e23
move getPosition out of component def
sodiumjoe May 14, 2016
867eaf5
add state wrapper to ToolTip
sodiumjoe May 14, 2016
584e161
Merge branch 'release/1.0.0' into feature/ANXR-202-tooltip
sodiumjoe May 16, 2016
4e703f8
move banner to `Communication` category
sodiumjoe May 16, 2016
9fc344c
remove unnecessary initial state
sodiumjoe May 16, 2016
0325c71
uncomment static directions example
sodiumjoe May 16, 2016
f2c1633
fix typo
sodiumjoe May 16, 2016
e7b9f31
fix lint errors
sodiumjoe May 16, 2016
3ad9cd8
add tooltip reducers tests
sodiumjoe May 16, 2016
acf895c
refactor onX convention test for better error output
sodiumjoe May 16, 2016
4e805a4
exempt getAlignmentOffset from onX convention
sodiumjoe May 16, 2016
3500090
refactor scoped class test for better errors
sodiumjoe May 16, 2016
ef34797
remove intermediate div
sodiumjoe May 16, 2016
445389c
scope direction/alignment classes
sodiumjoe May 16, 2016
8720841
add default maxWidth to ToolTip flyout
sodiumjoe May 16, 2016
3baf1f3
follow component class name convention
sodiumjoe May 17, 2016
86d9f60
add tests for ToolTip
sodiumjoe May 17, 2016
542c5ba
fix class name
sodiumjoe May 17, 2016
e4cb665
add isCloseable/onClose props to ToolTip
sodiumjoe May 17, 2016
7b731bf
add `kind` prop to ToolTip and clean up less
sodiumjoe May 17, 2016
c30a68e
change default maxWidth of ToolTip
sodiumjoe May 17, 2016
52f5778
add examples for `kind`, `isCloseable`
sodiumjoe May 17, 2016
6a07f32
style tweaks for ToolTip
sodiumjoe May 17, 2016
0143d3a
code review feedback
sodiumjoe May 18, 2016
43b3b8c
fix indents
sodiumjoe May 19, 2016
60ceb3c
Merge branch 'release/1.0.0' into feature/ANXR-202-tooltip
sodiumjoe May 19, 2016
8ec5bcc
className tweaks to ToolTip
sodiumjoe May 19, 2016
491de66
Merge branch 'release/1.0.0' into feature/ANXR-202-tooltip
sodiumjoe May 19, 2016
c8ad6fe
code review feedback on ToolTip
sodiumjoe May 19, 2016
d571cb2
fix test
sodiumjoe May 19, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/Banner/Banner.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const defaultIcons = {

/**
*
* {"categories": ["controls", "banners"], "madeFrom": ["DangerIcon", "InfoIcon", "SuccessIcon", "WarningIcon"]}
* {"categories": ["controls", "communication"], "madeFrom": ["DangerIcon", "InfoIcon", "SuccessIcon", "WarningIcon"]}
*
* A basic Banner. Any props that are not explicitly called out below will be
* passed through to the native `Banner` component.
Expand Down
207 changes: 186 additions & 21 deletions src/components/ContextMenu/ContextMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const {
bool,
node,
func,
number,
object,
oneOf,
string
Expand Down Expand Up @@ -40,9 +41,25 @@ const ContextMenu = createClass({
*/
style: object,
/**
* alignment of the FlyOut relative to Target. Defaults to `'down'`.
* direction of the FlyOut relative to Target. Defaults to `'down'`.
*/
direction: oneOf(['down', 'up']),
direction: oneOf(['down', 'up', 'right', 'left']),
/**
* the px offset along the axis of the direction
*/
directonOffset: number,
/**
* alignment of the Flyout relative to Target in the cross axis from `direction` Defaults to `'start'`
*/
alignment: oneOf(['start', 'center', 'end']),
/**
* the px offset along the axis of the alignment
*/
alignmentOffset: number,
/**
* an alternative to `alignmentOffset`, a function that is applied with the width/height of the flyout. the result is used as the `alignmentOffset`
*/
getAlignmentOffset: func,
/**
* Indicates whether the FlyOut will render or not. Defaults to `true`.
*/
Expand Down Expand Up @@ -74,6 +91,10 @@ const ContextMenu = createClass({
getDefaultProps() {
return {
direction: 'down',
directonOffset: 0,
alignment: 'start',
// no default alignmentOffset so it can default to result of `getAlignmentOffset`
getAlignmentOffset: _.constant(0),
isExpanded: true,
onClickOut: null,
portalId: null
Expand All @@ -94,7 +115,8 @@ const ContextMenu = createClass({
height: 0,
width: 0
},
flyOutHeight: 0
flyOutHeight: 0,
flyOutWidth: 0,
};
},

Expand All @@ -117,15 +139,20 @@ const ContextMenu = createClass({

componentWillUnmount() {
clearInterval(this.updateTargetRectangleIntervalId);
window.document.body.removeEventListener('click', this.onClickBodyEventListener);
document.body.removeEventListener('click', this.onClickBodyEventListener);
},

componentWillReceiveProps() {
this.alignFlyOut();
},

statics: {
CENTER: 'center',
DOWN: 'down',
END: 'end',
LEFT: 'left',
RIGHT: 'right',
START: 'start',
UP: 'up',
},

Expand All @@ -143,28 +170,46 @@ const ContextMenu = createClass({
}

const targetRect = getAbsoluteBoundingClientRect(target);
const flyOutEl = this.refs.flyOutPortal.portalElement.firstChild;

if (!flyOutPortal) {
return this.setState({
targetRect
});
}

const flyOutEl = flyOutPortal.portalElement.firstChild;
const {
height,
width,
} = flyOutEl.getBoundingClientRect();
this.setState({
targetRect,
flyOutHeight: flyOutEl.getBoundingClientRect().height
flyOutHeight: height,
flyOutWidth: width
});
},

render() {
const {
className,
style,
isExpanded,
direction,
...passThroughs
} = this.props;

const {
portalId,
targetRect,
flyOutHeight
} = this.state;
props: {
alignment,
alignmentOffset,
getAlignmentOffset,
className,
direction,
directonOffset,
isExpanded,
position,
style,
...passThroughs
},
state: {
portalId,
targetRect,
flyOutHeight,
flyOutWidth
}
} = this;

const targetElement = _.first(findTypes(this.props, ContextMenu.Target));
const targetChildren = _.get(targetElement, 'props.children', null);
Expand All @@ -186,10 +231,17 @@ const ContextMenu = createClass({
portalId={portalId}
style={_.assign({}, flyProps.style, {
position: 'absolute',
left: targetRect.left,
minWidth: targetRect.width,
top: (direction === ContextMenu.UP ? targetRect.top - flyOutHeight : targetRect.bottom)
})}
}, getFlyoutPosition({
direction,
alignment,
targetRect,
flyOutHeight,
flyOutWidth,
directonOffset,
getAlignmentOffset,
alignmentOffset,
}))}
>
{flyProps.children}
</Portal>
Expand All @@ -200,3 +252,116 @@ const ContextMenu = createClass({
});

export default ContextMenu;

function getFlyoutPosition({
direction,
alignment,
targetRect,
flyOutHeight,
flyOutWidth,
directonOffset,
getAlignmentOffset,
alignmentOffset = alignment === ContextMenu.CENTER
? getAlignmentOffset(_.includes([ContextMenu.UP, ContextMenu.DOWN], direction) ? flyOutWidth : flyOutHeight)
: 0,
}) {

const {
CENTER,
DOWN,
END,
LEFT,
RIGHT,
START,
UP,
} = ContextMenu;

const {
bottom,
left,
right,
top,
width,
height,
} = targetRect;

const {
clientWidth,
} = document.body;

const matcher = _.matches({ direction, alignment });

if (matcher({ direction: UP, alignment: START })) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, very readable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be even more readable if it was called matches or isMatch instead of matcher

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't want to clash with _.matches, and wanted to make it clear it's a function. 🤷

return {
top: top - flyOutHeight - directonOffset,
left: left - alignmentOffset
};
}
if (matcher({ direction: UP, alignment: END })) {
return {
top: top - flyOutHeight - directonOffset,
right: clientWidth - right - alignmentOffset
};
}
if (matcher({ direction: UP, alignment: CENTER })) {
return {
top: top - flyOutHeight - directonOffset,
left: left + (width / 2) - (flyOutWidth / 2) + alignmentOffset
};
}
if (matcher({ direction: DOWN, alignment: START })) {
return {
top: bottom + directonOffset,
left: left - alignmentOffset
};
}
if (matcher({ direction: DOWN, alignment: END })) {
return {
top: bottom + directonOffset,
right: clientWidth - right - alignmentOffset
};
}
if (matcher({ direction: DOWN, alignment: CENTER })) {
return {
top: bottom + directonOffset,
left: left + (width / 2) - (flyOutWidth / 2) + alignmentOffset
};
}
if (matcher({ direction: LEFT, alignment: START })) {
return {
top: top - alignmentOffset,
right: clientWidth - left + directonOffset
};
}
if (matcher({ direction: LEFT, alignment: END })) {
return {
top: top - flyOutHeight + height + alignmentOffset,
right: clientWidth - left + directonOffset
};
}
if (matcher({ direction: LEFT, alignment: CENTER })) {
return {
top: top - (flyOutHeight / 2) + (height / 2) + alignmentOffset,
right: clientWidth - left + directonOffset
};
}
if (matcher({ direction: RIGHT, alignment: START })) {
return {
top: top - alignmentOffset,
left: left + width + directonOffset
};
}
if (matcher({ direction: RIGHT, alignment: END })) {
return {
top: top - flyOutHeight + height + alignmentOffset,
left: left + width + directonOffset
};
}
if (matcher({ direction: RIGHT, alignment: CENTER })) {
return {
top: top - (flyOutHeight / 2) + (height / 2) + alignmentOffset,
left: left + width + directonOffset
};
}

}
2 changes: 1 addition & 1 deletion src/components/ContextMenu/examples/3.menu-bar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default React.createClass({
editDirection
} = this.state;
return (
<section style={{ height: 300 }}>
<section>

<ContextMenu
portalId='FileMenu-example'
Expand Down
Loading