Skip to content

Instantly share code, notes, and snippets.

@k-fish
Last active February 27, 2018 16:57
Show Gist options
  • Save k-fish/9f24c6714ed0656f45479256c144078b to your computer and use it in GitHub Desktop.
Save k-fish/9f24c6714ed0656f45479256c144078b to your computer and use it in GitHub Desktop.
Tooltip with basic-dropdown
import Ember from 'ember';
const VERTICAL_POSITIONS = {
ABOVE: 'above',
BELOW: 'below'
};
const HORIZONTAL_POSITIONS = {
LEFT: 'left',
RIGHT: 'right'
};
const PADDING = 20;
const CONTENT_HORIZONTAL_OFFSET = 7;
const ARROW_WIDTH = 19;
const _calculateHorizontalPosition = function({ triggerLeft, triggerWidth, contentWidth }, { horizontalPosition }) {
let left = triggerLeft + triggerWidth;
if (horizontalPosition === HORIZONTAL_POSITIONS.LEFT) {
left = triggerLeft - contentWidth;
}
return {
left
};
};
const _calculateVerticalPosition = function({ triggerTop, triggerHeight, contentHeight }) {
const top = triggerTop + window.pageYOffset + (triggerHeight / 2) - (contentHeight / 2);
return {
top
};
};
const _constrainContentWidth = function({ contentTop, contentLeft, contentWidth, contentHeight, triggerWidth }, { horizontalPosition, verticalPosition }) {
const triggerWidthConstraint = horizontalPosition ? CONTENT_HORIZONTAL_OFFSET + ARROW_WIDTH + triggerWidth : 0;
const maxAllowedWidth = document.documentElement.clientWidth - PADDING * 2 - triggerWidthConstraint;
const deltaContentWidth = maxAllowedWidth - contentWidth;
const options = {};
if (maxAllowedWidth > contentWidth) {
// degrade to vertical position
options.horizontalPosition = null;
}
return options;
};
export default Ember.Component.extend({
didReceiveAttrs() {
this._super(...arguments);
const horizontalPosition = this.get('horizontalPosition');
if (horizontalPosition && !Object.values(HORIZONTAL_POSITIONS).includes(horizontalPosition)) {
throw `horizontalPosition "${horizontalPosition}" is not a valid option`;
};
const verticalPosition = this.get('verticalPosition');
if (verticalPosition && !Object.values(VERTICAL_POSITIONS).includes(verticalPosition)) {
throw `verticalPosition "${verticalPosition}" is not a valid option`;
};
},
_horizontalPosition: Ember.computed('horizontalPosition', function() {
return this.get('horizontalPosition') || HORIZONTAL_POSITIONS.RIGHT;
}),
_verticalPosition: Ember.computed('verticalPosition', function() {
return this.get('verticalPosition') || VERTICAL_POSITIONS.ABOVE;
}),
_contentPositionClasses: Ember.computed('_horizontalPosition', function() {
return `arrow-content arrow-${this.get('_horizontalPosition')}-content`;
}),
calculatePosition(options, trigger, content) {
const triggerClientBoundingRect = trigger.getBoundingClientRect();
const contentClientBoundingRect = content.getBoundingClientRect();
const { top: triggerTop, left: triggerLeft, width: triggerWidth, height: triggerHeight } = triggerClientBoundingRect;
const { top: contentTop, left: contentLeft, height: contentHeight, width: contentWidth } = contentClientBoundingRect;
const boundingSizes = {
triggerTop, triggerLeft, triggerWidth, triggerHeight, contentHeight, contentWidth, contentTop, contentLeft
};
const _constrainedWidthOptions = _constrainContentWidth(boundingSizes, options);
Object.assign(options, _constrainedWidthOptions);
const horizontalPosition = _calculateHorizontalPosition(boundingSizes, options);
const verticalPosition = _calculateVerticalPosition(boundingSizes, options);
const style = Object.assign({}, constrainedContentWidth, horizontalPosition, verticalPosition);
return {
style
};
}
});
import Ember from 'ember';
export default Ember.Controller.extend({
apiSet: Ember.computed('dropapi', function() {
return this.get('dropapi');
})
});
body {
margin: 12px 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12pt;
}
.ember-basic-dropdown-content {
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
}
.center {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.arrow-content {
background: #fff;
border: 1px solid rgba(0,0,0,.15)
}
.arrow-left-content {
margin-left: -10px;
}
.arrow-right-content {
margin-left: 10px;
}
.arrow-left-content:after,.arrow-left-content:before {
left: 100%;
top: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none
}
.arrow-left-content:after {
border-color: rgba(255,255,255,0);
border-left-color: #fff;
border-width: 10px;
margin-top: -10px
}
.arrow-left-content:before {
border-color: transparent;
border-left-color: rgba(0,0,0,.15);
border-width: 11px;
margin-top: -11px
}
.arrow-right-content:after,.arrow-right-content:before {
right: 100%;
top: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none
}
.arrow-right-content:after {
border-color: rgba(255,255,255,0);
border-right-color: #fff;
border-width: 10px;
margin-top: -10px
}
.arrow-right-content:before {
border-color: transparent;
border-right-color: rgba(0,0,0,.15);
border-width: 11px;
margin-top: -11px
}
<h1>Tooltip with basic-dropdown example</h1>
<div id="wormhole-destination">
</div>
<div>
{{#dropdown-as-tooltip
verticalPosition="below"
horizontalPosition="left"
destination="wormhole-destination"
registerAPI=(action (mut tooltipAPI))
as |tooltip|
}}
{{#tooltip.target}}
<input value="My data here">
{{/tooltip.target}}
{{#tooltip.content}}
This data is not valid. Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum
{{/tooltip.content}}
{{/dropdown-as-tooltip}}
<br>
<br>
<button {{action tooltipAPI.actions.open}}>Display tooltip on the right</button>
</div>
<br>
<br>
<br>
<div>
{{#dropdown-as-tooltip
destination="wormhole-destination"
horizontalPosition="left"
registerAPI=(action (mut secondTooltipAPI))
as |tooltip|
}}
{{#tooltip.target}}
<input value="My data here">
{{/tooltip.target}}
{{#tooltip.content}}
This data is not valid.
{{/tooltip.content}}
{{/dropdown-as-tooltip}}
<br>
<br>
<button {{action secondTooltipAPI.actions.open}}>Display tooltip on the left</button>
</div>
{{#basic-dropdown
calculatePosition=(action calculatePosition (hash horizontalPosition=_horizontalPosition verticalPosition=_verticalPosition))
destination="wormhole-destination"
registerAPI=registerAPI
as |dropdown|
}}
{{yield
(hash
target=(component 'dropdown-as-tooltip/tooltip-target' dropdown=dropdown tagName='')
content=(component dropdown.content class=_contentPositionClasses)
)
}}
{{/basic-dropdown}}
<div data-ebd-id="{{dropdown.uniqueId}}-trigger" style="display: inline-block">
{{yield}}
</div>
{
"version": "0.13.0",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"ember": "2.16.2",
"ember-template-compiler": "2.16.2",
"ember-testing": "2.16.2"
},
"addons": {
"ember-data": "2.16.3",
"ember-basic-dropdown": "0.33.0",
"ember-composable-helpers": "2.1.0",
"ember-truth-helpers": "2.0.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment