ui: Modifier based tooltips (#9288)
This commit is contained in:
parent
e32cd6b55c
commit
62850759c5
|
@ -20,8 +20,9 @@
|
|||
<header>
|
||||
<h2>
|
||||
{{chain.ServiceName}} Router
|
||||
<span>
|
||||
<em role="tooltip">Use routers to intercept traffic using L7 criteria such as path prefixes or http headers.</em>
|
||||
<span
|
||||
{{tooltip 'Use routers to intercept traffic using L7 criteria such as path prefixes or http headers.'}}
|
||||
>
|
||||
</span>
|
||||
</h2>
|
||||
</header>
|
||||
|
@ -38,8 +39,9 @@
|
|||
<header>
|
||||
<h2>
|
||||
Splitters
|
||||
<span>
|
||||
<em role="tooltip">Splitters are configured to split incoming requests across different services or subsets of a single service.</em>
|
||||
<span
|
||||
{{tooltip 'Splitters are configured to split incoming requests across different services or subsets of a single service.'}}
|
||||
>
|
||||
</span>
|
||||
</h2>
|
||||
</header>
|
||||
|
@ -56,8 +58,8 @@
|
|||
<header>
|
||||
<h2>
|
||||
Resolvers
|
||||
<span>
|
||||
<em role="tooltip">Resolvers are used to define which instances of a service should satisfy discovery requests.</em>
|
||||
<span {{tooltip "Resolvers are used to define which instances of a service should satisfy discovery requests."}}>
|
||||
|
||||
</span>
|
||||
</h2>
|
||||
</header>
|
||||
|
@ -70,10 +72,15 @@
|
|||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
<svg width="100%" height="100%" viewBox={{concat '0 0 ' width ' ' height}} preserveAspectRatio="none">
|
||||
<svg class="edges"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox={{concat '0 0 ' width ' ' height}}
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
{{#each routes as |item|}}
|
||||
{{#let (dom-position (concat '#' item.ID)) as |src|}}
|
||||
{{#let (dom-position (concat '#' item.NextNode)) as |destRect|}}
|
||||
{{#let (dom-position (concat '#' item.ID) '.edges') as |src|}}
|
||||
{{#let (dom-position (concat '#' item.NextNode) '.edges') as |destRect|}}
|
||||
{{#let (tween-to (hash
|
||||
x=destRect.x
|
||||
y=(add destRect.y (div destRect.height 2))
|
||||
|
@ -93,17 +100,17 @@
|
|||
{{/let}}
|
||||
{{/each}}
|
||||
{{#each splitters as |splitter|}}
|
||||
{{#let (dom-position (concat '#' splitter.ID)) as |src|}}
|
||||
{{#let (dom-position (concat '#' splitter.ID) '.edges') as |src|}}
|
||||
{{#each splitter.Splits as |item index|}}
|
||||
{{#let (dom-position (concat '#' item.NextNode)) as |destRect|}}
|
||||
{{#let (dom-position (concat '#' item.NextNode) '.edges') as |destRect|}}
|
||||
{{#let (tween-to (hash
|
||||
x=destRect.x
|
||||
y=(add destRect.y (div destRect.height 2))
|
||||
) (concat splitter.ID '-' index)) as |dest|}}
|
||||
<path
|
||||
{{tooltip (concat (round (or item.Weight 0) decimals=2) '%') options=(hash followCursor=true)}}
|
||||
id={{concat 'splitter:' splitter.Name '>' item.NextNode}}
|
||||
class="split"
|
||||
data-percentage={{or item.Weight 0}}
|
||||
d={{
|
||||
svg-curve (hash
|
||||
x=dest.x
|
||||
|
@ -121,14 +128,14 @@
|
|||
<svg class="resolver-inlets" viewBox={{concat '0 0 10 ' height}}>
|
||||
{{#each routes as |item|}}
|
||||
{{#if (starts-with 'resolver:' item.NextNode) }}
|
||||
{{#let (dom-position (concat '#' item.NextNode)) as |dest|}}
|
||||
{{#let (dom-position (concat '#' item.NextNode) '.edges') as |dest|}}
|
||||
<circle r="2.5" cx="5" cy={{add dest.y (div dest.height 2)}} />
|
||||
{{/let}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{#each splitters as |item|}}
|
||||
{{#each item.Splits as |item|}}
|
||||
{{#let (dom-position (concat '#' item.NextNode)) as |dest|}}
|
||||
{{#let (dom-position (concat '#' item.NextNode) '.edges') as |dest|}}
|
||||
<circle r="2.5" cx="5" cy={{add dest.y (div dest.height 2)}} />
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
|
@ -137,12 +144,9 @@
|
|||
<svg class="splitter-inlets" viewBox={{concat '0 0 10 ' height}}>
|
||||
{{#each routes as |item|}}
|
||||
{{#if (starts-with 'splitter:' item.NextNode) }}
|
||||
{{#let (dom-position (concat '#' item.NextNode)) as |dest|}}
|
||||
{{#let (dom-position (concat '#' item.NextNode) '.edges') as |dest|}}
|
||||
<circle r="2.5" cx="5" cy={{add dest.y (div dest.height 2)}} />
|
||||
{{/let}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</svg>
|
||||
<div class={{concat 'tooltip' (if activeTooltip ' active' '')}} style={{{ concat 'top:' y 'px;left:' x 'px;'}}}>
|
||||
<span role="tooltip">{{round tooltip decimals=2}}%</span>
|
||||
</div>
|
||||
|
|
|
@ -11,21 +11,22 @@ export default Component.extend({
|
|||
dataStructs: service('data-structs'),
|
||||
classNames: ['discovery-chain'],
|
||||
classNameBindings: ['active'],
|
||||
isDisplayed: false,
|
||||
selectedId: '',
|
||||
x: 0,
|
||||
y: 0,
|
||||
tooltip: '',
|
||||
activeTooltip: false,
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners = this.dom.listeners();
|
||||
},
|
||||
didReceiveAttrs: function() {
|
||||
this._super(...arguments);
|
||||
if (this.element) {
|
||||
this.addPathListeners();
|
||||
}
|
||||
didInsertElement: function() {
|
||||
this._listeners.add(this.dom.document(), {
|
||||
click: e => {
|
||||
// all route/splitter/resolver components currently
|
||||
// have classes that end in '-card'
|
||||
if (!this.dom.closest('[class$="-card"]', e.target)) {
|
||||
set(this, 'active', false);
|
||||
set(this, 'selectedId', '');
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
|
@ -122,53 +123,7 @@ export default Component.extend({
|
|||
height: computed('chain.{Nodes,Targets}', function() {
|
||||
return this.element.offsetHeight;
|
||||
}),
|
||||
// TODO(octane): ember has trouble adding mouse events to svg elements whilst giving
|
||||
// the developer access to the mouse event therefore we just use JS to add our events
|
||||
// revisit this post Octane
|
||||
addPathListeners: function() {
|
||||
schedule('afterRender', () => {
|
||||
this._listeners.remove();
|
||||
// as this is now afterRender, theoretically
|
||||
// it could happen after the component is destroyed?
|
||||
// watch for that incase
|
||||
if (this.element && !this.isDestroyed) {
|
||||
this._listeners.add(this.dom.document(), {
|
||||
click: e => {
|
||||
// all route/splitter/resolver components currently
|
||||
// have classes that end in '-card'
|
||||
if (!this.dom.closest('[class$="-card"]', e.target)) {
|
||||
set(this, 'active', false);
|
||||
set(this, 'selectedId', '');
|
||||
}
|
||||
},
|
||||
});
|
||||
[...this.dom.elements('path.split', this.element)].forEach(item => {
|
||||
this._listeners.add(item, {
|
||||
mouseover: e => this.actions.showSplit.apply(this, [e]),
|
||||
mouseout: e => this.actions.hideSplit.apply(this, [e]),
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
// TODO: currently don't think there is a way to listen
|
||||
// for an element being removed inside a component, possibly
|
||||
// using IntersectionObserver. It's a tiny detail, but we just always
|
||||
// remove the tooltip on component update as its so tiny, ideal
|
||||
// the tooltip would stay if there was no change to the <path>
|
||||
// set(this, 'activeTooltip', false);
|
||||
},
|
||||
actions: {
|
||||
showSplit: function(e) {
|
||||
this.setProperties({
|
||||
x: e.clientX,
|
||||
y: e.clientY - 3,
|
||||
tooltip: e.target.dataset.percentage,
|
||||
activeTooltip: true,
|
||||
});
|
||||
},
|
||||
hideSplit: function(e = null) {
|
||||
set(this, 'activeTooltip', false);
|
||||
},
|
||||
click: function(e) {
|
||||
const id = e.currentTarget.getAttribute('id');
|
||||
if (id === this.selectedId) {
|
||||
|
|
|
@ -25,17 +25,6 @@
|
|||
%discovery-chain [id*=':']:not(path):hover {
|
||||
@extend %chain-node-active;
|
||||
}
|
||||
%discovery-chain .tooltip {
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
}
|
||||
%discovery-chain .tooltip.active > [role='tooltip'],
|
||||
%discovery-chain .tooltip.active > [role='tooltip']::after {
|
||||
@extend %blink-in-fade-out-active;
|
||||
}
|
||||
%resolver-card dt {
|
||||
@extend %with-pseudo-tooltip, %tooltip-right;
|
||||
}
|
||||
|
||||
%discovery-chain {
|
||||
position: relative;
|
||||
|
@ -49,6 +38,9 @@
|
|||
padding: 10px 1% 10px 1%;
|
||||
width: 32%;
|
||||
}
|
||||
%chain-group > header {
|
||||
height: 18px;
|
||||
}
|
||||
%chain-group > header span {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
@ -102,7 +94,8 @@
|
|||
}
|
||||
%route-card section header {
|
||||
display: block;
|
||||
width: 33px;
|
||||
width: 19px;
|
||||
margin-right: 14px
|
||||
}
|
||||
/**/
|
||||
/* resolver */
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
<h3>{{@item.Name}}</h3>
|
||||
{{#if item.Failover}}
|
||||
<dl class="failover">
|
||||
<dt data-tooltip={{concat @item.Failover.Type ' failover'}}>
|
||||
<dt
|
||||
{{tooltip (concat @item.Failover.Type ' failover')}}
|
||||
>
|
||||
{{concat @item.Failover.Type ' failover'}}
|
||||
</dt>
|
||||
<dd>
|
||||
|
@ -30,14 +32,22 @@
|
|||
<a name="">
|
||||
{{#if child.Redirect}}
|
||||
<dl class="redirect">
|
||||
<dt data-tooltip="Redirect">Redirect</dt>
|
||||
<dt
|
||||
{{tooltip "Redirect"}}
|
||||
>
|
||||
Redirect
|
||||
</dt>
|
||||
<dd>
|
||||
{{child.Name}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{#if child.Failover}}
|
||||
<dl class="failover">
|
||||
<dt data-tooltip={{concat child.Failover.Type ' failover'}}>{{concat child.Failover.Type ' failover'}}</dt>
|
||||
<dt
|
||||
{{tooltip (concat child.Failover.Type ' failover')}}
|
||||
>
|
||||
{{concat child.Failover.Type ' failover'}}
|
||||
</dt>
|
||||
<dd>
|
||||
<ol>
|
||||
{{#each child.Failover.Targets as |target|}}
|
||||
|
@ -52,7 +62,11 @@
|
|||
{{else if child.Failover}}
|
||||
{{child.Name}}
|
||||
<dl class="failover">
|
||||
<dt data-tooltip={{concat child.Failover.Type ' failover'}}>{{concat child.Failover.Type ' failover'}}</dt>
|
||||
<dt
|
||||
{{tooltip (concat child.Failover.Type ' failover')}}
|
||||
>
|
||||
{{concat child.Failover.Type ' failover'}}
|
||||
</dt>
|
||||
<dd>
|
||||
<ol>
|
||||
{{#each child.Failover.Targets as |target|}}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
</header>
|
||||
{{#if (gt @item.Definition.Match.HTTP.Header.length 0) }}
|
||||
<section class="match-headers">
|
||||
<header data-tooltip="Header">
|
||||
<header {{tooltip 'Header'}}>
|
||||
<h4>Headers</h4>
|
||||
</header>
|
||||
<dl>
|
||||
|
@ -39,7 +39,7 @@
|
|||
{{/if}}
|
||||
{{#if (gt @item.Definition.Match.HTTP.QueryParam.length 0) }}
|
||||
<section class="match-queryparams">
|
||||
<header data-tooltip="Query Params">
|
||||
<header {{tooltip 'Query Params'}}>
|
||||
<h4>Query Params</h4>
|
||||
</header>
|
||||
<dl>
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
/* the styling there almost 100% uses our CSS vars */
|
||||
/* defined in our CSS files, but be sure to */
|
||||
/* take a look in the discovery-chain.hbs */
|
||||
%discovery-chain .tooltip,
|
||||
%chain-group > header span {
|
||||
@extend %with-tooltip;
|
||||
}
|
||||
%route-card > header ul li {
|
||||
@extend %pill-500, %frame-gray-900;
|
||||
}
|
||||
|
@ -63,14 +59,6 @@
|
|||
height: 1.2em;
|
||||
opacity: 0.6;
|
||||
}
|
||||
/* TODO: this is tooltip related, we also do this elsewhere */
|
||||
/* so would be good to look and see if we can centralize this */
|
||||
%chain-group > header span em {
|
||||
text-transform: none;
|
||||
width: 250px;
|
||||
font-style: normal;
|
||||
white-space: normal !important;
|
||||
}
|
||||
%chain-node {
|
||||
@extend %discovery-chain-tween;
|
||||
transition-property: opacity background-color border-color;
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
<StateChart @src={{chart}} as |State Guard Action dispatch state|>
|
||||
<Ref @target={{this}} @name="dispatch" @value={{dispatch}} />
|
||||
<State @matches="success">
|
||||
<Tooltip @targetId={{guid}} @isShown={{true}} @position={{position}} @duration={{3000}} @oncomplete={{action dispatch 'RESET'}}>
|
||||
<span role="alert">Copied {{name}}!</span>
|
||||
</Tooltip>
|
||||
</State>
|
||||
<State @matches="error">
|
||||
<Tooltip role="alert" @targetId={{guid}} @isShown={{true}} @position={{position}} @duration={{3000}} @oncomplete={{action dispatch 'RESET'}}>
|
||||
<span role="alert">There was an problem!</span>
|
||||
</Tooltip>
|
||||
</State>
|
||||
<div class="copy-button" id={{guid}}>
|
||||
<button title={{concat "Copy " name " to the clipboard"}} ...attributes type="button" class="copy-btn" data-clipboard-text={{value}}>{{~yield~}}</button>
|
||||
<div
|
||||
{{did-insert this.connect}}
|
||||
{{will-destroy this.disconnect}}
|
||||
class="copy-button"
|
||||
id={{this.guid}}
|
||||
...attributes
|
||||
>
|
||||
<button
|
||||
title={{concat "Copy " @name " to the clipboard"}}
|
||||
type="button"
|
||||
class="copy-btn"
|
||||
data-clipboard-text={{@value}}
|
||||
...attributes
|
||||
{{tooltip
|
||||
(if (state-matches state 'success') (concat 'Copied ' @name '!!') 'There was a problem!')
|
||||
options=(hash
|
||||
trigger='manual'
|
||||
showOnCreate=(not (state-matches state 'idle'))
|
||||
delay=(array 0 3000)
|
||||
onHidden=(action dispatch 'RESET')
|
||||
)
|
||||
}}
|
||||
>
|
||||
{{~yield~}}
|
||||
</button>
|
||||
</div>
|
||||
</StateChart>
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
import Component from '@ember/component';
|
||||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import chart from './chart.xstate';
|
||||
|
||||
export default Component.extend({
|
||||
clipboard: service('clipboard/os'),
|
||||
dom: service('dom'),
|
||||
tagName: '',
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
export default class CopyButton extends Component {
|
||||
@service('clipboard/os') clipboard;
|
||||
@service('dom') dom;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.chart = chart;
|
||||
this.guid = this.dom.guid(this);
|
||||
this._listeners = this.dom.listeners();
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners.remove();
|
||||
},
|
||||
didInsertElement: function() {
|
||||
this._super(...arguments);
|
||||
}
|
||||
|
||||
@action
|
||||
connect() {
|
||||
this._listeners.add(this.clipboard.execute(`#${this.guid} button`), {
|
||||
success: () => this.dispatch('SUCCESS'),
|
||||
error: () => this.dispatch('ERROR'),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
disconnect() {
|
||||
this._listeners.remove();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,10 @@
|
|||
<dd>
|
||||
{{item.Type}}
|
||||
{{#if (and exposed (contains item.Type (array 'http' 'grpc')))}}
|
||||
<em data-test-exposed="true" data-tooltip="Expose.checks is set to true, so all registered HTTP and gRPC check paths are exposed through Envoy for the Consul agent.">Exposed</em>
|
||||
<em
|
||||
data-test-exposed="true"
|
||||
{{tooltip "Expose.checks is set to true, so all registered HTTP and gRPC check paths are exposed through Envoy for the Consul agent."}}
|
||||
>Exposed</em>
|
||||
{{/if}}
|
||||
</dd>
|
||||
</dl>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<EmberTooltip
|
||||
@targetId={{targetId}}
|
||||
@onHide={{oncomplete}}
|
||||
@isShown={{isShown}}
|
||||
@duration={{duration}}
|
||||
@event={{if isShown 'none' (or event 'hover')}}
|
||||
@popperContainer=".app-view"
|
||||
@side={{or position 'top'}}
|
||||
<div
|
||||
class="tooltip"
|
||||
...attributes
|
||||
{{tooltip
|
||||
options=(hash
|
||||
triggerTarget="parentNode"
|
||||
)
|
||||
}}
|
||||
>
|
||||
{{yield}}
|
||||
</EmberTooltip>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
});
|
|
@ -0,0 +1,119 @@
|
|||
[data-tippy-root] {
|
||||
@extend %tooltip-layer;
|
||||
}
|
||||
.tippy-box[data-theme~=tooltip] {
|
||||
& {
|
||||
@extend %tooltip-bubble;
|
||||
}
|
||||
&[data-placement^=bottom] > .tippy-arrow {
|
||||
@extend %tooltip-tail-bottom;
|
||||
}
|
||||
&[data-placement^=top] > .tippy-arrow {
|
||||
@extend %tooltip-tail-top;
|
||||
}
|
||||
&[data-placement^=left] > .tippy-arrow {
|
||||
@extend %tooltip-tail-left;
|
||||
}
|
||||
&[data-placement^=right] > .tippy-arrow {
|
||||
@extend %tooltip-tail-right;
|
||||
}
|
||||
.tippy-arrow {
|
||||
@extend %tooltip-tail;
|
||||
}
|
||||
.tippy-content {
|
||||
@extend %tooltip-content;
|
||||
}
|
||||
}
|
||||
|
||||
%tooltip-layer {
|
||||
max-width: calc(100vw - 10px);
|
||||
}
|
||||
%tooltip-bubble {
|
||||
& {
|
||||
position: relative;
|
||||
outline: 0;
|
||||
|
||||
background-color: $gray-700;
|
||||
color: $white;
|
||||
border-radius: $decor-radius-100;
|
||||
box-shadow: $decor-elevation-400;
|
||||
transition-property: transform, visibility, opacity;
|
||||
}
|
||||
&[data-animation=fade][data-state=hidden] {
|
||||
opacity: 0;
|
||||
}
|
||||
&[data-inertia][data-state=visible] {
|
||||
transition-timing-function: cubic-bezier(.54, 1.5, .38, 1.11);
|
||||
}
|
||||
}
|
||||
%tooltip-tail {
|
||||
--size: 5px;
|
||||
& {
|
||||
color: $gray-700;
|
||||
width: calc(var(--size) * 2);
|
||||
height: calc(var(--size) * 2);
|
||||
}
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
||||
border-color: $transparent;
|
||||
border-style: solid;
|
||||
}
|
||||
}
|
||||
%tooltip-tail-top {
|
||||
& {
|
||||
bottom: 0;
|
||||
}
|
||||
&::before {
|
||||
bottom: calc(0px - var(--size));
|
||||
left: 0;
|
||||
|
||||
border-width: var(--size) var(--size) 0;
|
||||
border-top-color: initial;
|
||||
transform-origin: center top;
|
||||
}
|
||||
}
|
||||
%tooltip-tail-bottom {
|
||||
& {
|
||||
top: 0
|
||||
}
|
||||
&::before {
|
||||
top: calc(0px - var(--size));
|
||||
left: 0;
|
||||
border-width: 0 var(--size) var(--size);
|
||||
border-bottom-color: initial;
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
}
|
||||
%tooltip-tail-left {
|
||||
& {
|
||||
right: 0;
|
||||
}
|
||||
&::before {
|
||||
right: calc(0px - var(--size));
|
||||
border-width: var(--size) 0 var(--size) var(--size);
|
||||
border-left-color: initial;
|
||||
transform-origin: center left;
|
||||
}
|
||||
}
|
||||
%tooltip-tail-right {
|
||||
& {
|
||||
left: 0
|
||||
}
|
||||
&::before {
|
||||
left: calc(0px - var(--size));
|
||||
border-width: var(--size) var(--size) var(--size) 0;
|
||||
border-right-color: initial;
|
||||
transform-origin: center right;
|
||||
}
|
||||
}
|
||||
|
||||
%tooltip-content {
|
||||
@extend %p3;
|
||||
padding: 12px;
|
||||
max-width: 192px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
@ -9,14 +9,18 @@
|
|||
<div class="stats">
|
||||
{{#if hasLoaded }}
|
||||
{{#each stats as |stat|}}
|
||||
<dl>
|
||||
<dl {{tooltip
|
||||
stat.desc
|
||||
options=(hash
|
||||
allowHTML=true
|
||||
)
|
||||
}}>
|
||||
<dt>
|
||||
{{stat.value}}
|
||||
</dt>
|
||||
<dd>
|
||||
{{stat.label}}
|
||||
</dd>
|
||||
<Tooltip>{{{stat.desc}}}</Tooltip>
|
||||
</dl>
|
||||
{{else}}
|
||||
<span>No Metrics Available</span>
|
||||
|
|
|
@ -1,28 +1,14 @@
|
|||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Helper.extend({
|
||||
dom: service('dom'),
|
||||
compute: function([selector, id], hash) {
|
||||
const $el = this.dom.element(selector);
|
||||
const $refs = [$el.offsetParent, $el];
|
||||
// TODO: helper probably needs to accept a `reference=` option
|
||||
// with a selector to use as reference/root
|
||||
if (selector.startsWith('#resolver:')) {
|
||||
$refs.unshift($refs[0].offsetParent);
|
||||
}
|
||||
return $refs.reduce(
|
||||
function(prev, item) {
|
||||
prev.x += item.offsetLeft;
|
||||
prev.y += item.offsetTop;
|
||||
return prev;
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
height: $el.offsetHeight,
|
||||
width: $el.offsetWidth,
|
||||
}
|
||||
);
|
||||
compute: function([target, from], hash) {
|
||||
const $target = this.dom.element(target);
|
||||
const $from = this.dom.element(from);
|
||||
const fromRect = $from.getBoundingClientRect();
|
||||
const rect = $target.getBoundingClientRect();
|
||||
rect.x = rect.x - fromRect.x;
|
||||
rect.y = rect.y - fromRect.y;
|
||||
return rect;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import { modifier } from 'ember-modifier';
|
||||
import tippy, { followCursor } from 'tippy.js';
|
||||
|
||||
/**
|
||||
* Tooltip modifier using Tippy.js
|
||||
* https://atomiks.github.io/tippyjs
|
||||
*
|
||||
* {{tooltip 'Text' options=(hash )}}
|
||||
*/
|
||||
export default modifier(($element, [content], hash = {}) => {
|
||||
const options = hash.options || {};
|
||||
|
||||
let $anchor = $element;
|
||||
|
||||
// make it easy to specify the modified element as the actual tooltip
|
||||
if (typeof options.triggerTarget === 'string') {
|
||||
const $el = $anchor;
|
||||
switch (options.triggerTarget) {
|
||||
case 'parentNode':
|
||||
$anchor = $anchor.parentNode;
|
||||
break;
|
||||
default:
|
||||
$anchor = $anchor.querySelectorAll(options.triggerTarget);
|
||||
}
|
||||
content = $anchor.cloneNode(true);
|
||||
$el.remove();
|
||||
hash.options.triggerTarget = undefined;
|
||||
}
|
||||
// {{tooltip}} will just use the HTML content
|
||||
if (typeof content === 'undefined') {
|
||||
content = $anchor.innerHTML;
|
||||
$anchor.innerHTML = '';
|
||||
}
|
||||
let interval;
|
||||
if (options.trigger === 'manual') {
|
||||
// if we are manually triggering, a out delay means only show for the
|
||||
// amount of time specified by the delay
|
||||
const delay = options.delay || [];
|
||||
if (typeof delay[1] !== 'undefined') {
|
||||
hash.options.onShown = tooltip => {
|
||||
clearInterval(interval);
|
||||
interval = setTimeout(() => {
|
||||
tooltip.hide();
|
||||
}, delay[1]);
|
||||
};
|
||||
}
|
||||
}
|
||||
let $trigger = $anchor;
|
||||
const tooltip = tippy($anchor, {
|
||||
theme: 'tooltip',
|
||||
triggerTarget: $trigger,
|
||||
content: $anchor => content,
|
||||
// showOnCreate: true,
|
||||
// hideOnClick: false,
|
||||
plugins: [typeof options.followCursor !== 'undefined' ? followCursor : undefined].filter(item =>
|
||||
Boolean(item)
|
||||
),
|
||||
...hash.options,
|
||||
});
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
tooltip.destroy();
|
||||
};
|
||||
});
|
|
@ -14,4 +14,3 @@
|
|||
@import './table/index';
|
||||
@import './tabs/index';
|
||||
@import './toggle-button/index';
|
||||
@import './tooltip/index';
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
@import './skin';
|
||||
@import './layout';
|
||||
|
||||
%with-pseudo-tooltip,
|
||||
%with-tooltip {
|
||||
@extend %tooltip;
|
||||
}
|
||||
%with-pseudo-tooltip::before,
|
||||
%with-tooltip [role='tooltip'] {
|
||||
@extend %tooltip-bubble;
|
||||
}
|
||||
%with-pseudo-tooltip::after,
|
||||
%with-tooltip [role='tooltip']::after {
|
||||
@extend %tooltip-tail;
|
||||
}
|
||||
|
||||
%with-pseudo-tooltip::after,
|
||||
%with-pseudo-tooltip::before,
|
||||
%with-tooltip [role='tooltip']::after,
|
||||
%with-tooltip [role='tooltip'] {
|
||||
@extend %blink-in-fade-out;
|
||||
}
|
||||
%with-pseudo-tooltip:hover::after,
|
||||
%with-pseudo-tooltip:hover::before,
|
||||
%with-pseudo-tooltip:focus::after,
|
||||
%with-pseudo-tooltip:focus::before,
|
||||
%with-tooltip:hover [role='tooltip']::after,
|
||||
%with-tooltip:hover [role='tooltip'],
|
||||
%with-tooltip:focus [role='tooltip']::after,
|
||||
%with-tooltip:focus [role='tooltip'] {
|
||||
@extend %blink-in-fade-out-active;
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
%tooltip {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
%tooltip-bubble,
|
||||
%tooltip-tail {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
%tooltip-bubble {
|
||||
padding: 12px;
|
||||
white-space: nowrap;
|
||||
content: attr(data-tooltip);
|
||||
text-indent: 0;
|
||||
min-width: 192px;
|
||||
}
|
||||
%tooltip-bubble {
|
||||
/* TODO: structure says left aligned, check this is correct */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
%tooltip-tail {
|
||||
content: '';
|
||||
transform: scale(1, 0.5);
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
/* TODO: positioning */
|
||||
%tooltip-bubble {
|
||||
bottom: calc(100% + 5px);
|
||||
}
|
||||
%tooltip-tail {
|
||||
left: 50%;
|
||||
margin-left: -9px;
|
||||
bottom: -13px;
|
||||
}
|
||||
/* TODO: Try and use the same vertical positioning all tooltips */
|
||||
/* this is only for pseudo tooltips be want to avoid */
|
||||
/* specifying pseudo in this file */
|
||||
%tooltip::after {
|
||||
bottom: calc(100% - 8px);
|
||||
}
|
||||
%tooltip-bottom::before {
|
||||
bottom: auto;
|
||||
top: calc(100% + 8px);
|
||||
}
|
||||
%tooltip-bottom::after {
|
||||
bottom: -12px;
|
||||
}
|
||||
|
||||
// Ember Tooltips
|
||||
.ember-tooltip {
|
||||
padding: 12px;
|
||||
max-width: 192px;
|
||||
z-index: 4;
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
%tooltip-bubble {
|
||||
color: $white;
|
||||
background-color: $gray-700;
|
||||
}
|
||||
%tooltip-tail {
|
||||
background-color: $transparent;
|
||||
border-color: $transparent;
|
||||
border-top-color: $gray-700;
|
||||
border-bottom-color: $gray-700;
|
||||
}
|
||||
|
||||
/* borders here are used to draw a triangle in CSS */
|
||||
/* they are not actual borders */
|
||||
|
||||
%tooltip-tail {
|
||||
border-style: solid;
|
||||
border-bottom-width: 0;
|
||||
border-top-width: 18px;
|
||||
border-left-width: 9px;
|
||||
border-right-width: 9px;
|
||||
}
|
||||
%tooltip-bottom::after {
|
||||
border-top-width: 0;
|
||||
border-bottom-width: 18px;
|
||||
}
|
||||
%tooltip-bubble {
|
||||
border-radius: $decor-radius-100;
|
||||
box-shadow: $decor-elevation-400;
|
||||
}
|
||||
|
||||
// Ember Tooltips
|
||||
.ember-tooltip {
|
||||
background-color: $gray-700;
|
||||
border-radius: $decor-radius-100;
|
||||
}
|
||||
.ember-tooltip[x-placement^='top'] .ember-tooltip-arrow {
|
||||
border-top-color: $gray-700;
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
@import './components/tabs';
|
||||
@import './components/pill';
|
||||
@import './components/table';
|
||||
@import './components/tooltip';
|
||||
@import './components/tag-list';
|
||||
@import './components/healthcheck-output';
|
||||
@import './components/freetext-filter';
|
||||
|
@ -46,11 +45,12 @@
|
|||
@import './components/loader';
|
||||
@import './components/main-header-horizontal';
|
||||
@import './components/main-nav-horizontal';
|
||||
@import './components/app-view';
|
||||
@import './components/footer';
|
||||
|
||||
/**/
|
||||
|
||||
@import './components/app-view';
|
||||
@import 'consul-ui/components/tooltip';
|
||||
@import 'consul-ui/components/notice';
|
||||
@import 'consul-ui/components/modal-dialog';
|
||||
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
.app-view {
|
||||
@extend %app-view;
|
||||
}
|
||||
%app-view-actions label + div {
|
||||
/* We need this extra to allow tooltips to show */
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
%app-view header form {
|
||||
@extend %filter-bar;
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
.consul-lock-session-list ul > li:not(:first-child) {
|
||||
@extend %with-one-action-row;
|
||||
}
|
||||
/*TODO: This hides the icons-less dt's in the below lists as */
|
||||
/* they don't have tooltips */
|
||||
// TODO: This hides the icons-less dt's in the below lists as they don't have
|
||||
// tooltips the todo would be to wrap these texts in spans
|
||||
.consul-nspace-list > ul > li:not(:first-child) dt,
|
||||
.consul-token-list > ul > li:not(:first-child) dt,
|
||||
.consul-policy-list > ul li:not(:first-child) dl:not(.datacenter) dt,
|
||||
|
@ -57,13 +57,11 @@
|
|||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
/* buttons need to be displayed in order for the tooltip */
|
||||
/* to track them */
|
||||
%composite-row-header .copy-button button {
|
||||
opacity: 0;
|
||||
display: none;
|
||||
}
|
||||
%composite-row-header:hover .copy-button button {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
}
|
||||
%composite-row .copy-button button:hover {
|
||||
background-color: transparent !important;
|
||||
|
|
|
@ -2,13 +2,3 @@
|
|||
.healthcheck-output {
|
||||
@extend %healthcheck-output;
|
||||
}
|
||||
%healthcheck-output dd em[data-tooltip] {
|
||||
@extend %with-pseudo-tooltip;
|
||||
}
|
||||
%healthcheck-output dd em::before {
|
||||
width: 250px;
|
||||
/* TODO: All tooltips previously used */
|
||||
/* nowrap, they shouldn't */
|
||||
white-space: normal !important;
|
||||
}
|
||||
/**/
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/* override structure min-width for the moment */
|
||||
/* TODO: Clarify whether these should actually use */
|
||||
/* the min-width from structure */
|
||||
/* TODO: See if we can move all these to base */
|
||||
%tooltip-bubble {
|
||||
min-width: 0;
|
||||
}
|
||||
%tooltip-below::after {
|
||||
top: calc(100% - 8px);
|
||||
bottom: auto;
|
||||
border-top: none;
|
||||
border-bottom: 18px solid $gray-500;
|
||||
}
|
||||
%tooltip-below::before {
|
||||
top: calc(100% + 4px);
|
||||
bottom: auto;
|
||||
/*TODO: This should probably go into base*/
|
||||
line-height: 1em;
|
||||
}
|
||||
%tooltip-left::before {
|
||||
right: 0;
|
||||
}
|
||||
%tooltip-right::before {
|
||||
left: -7px;
|
||||
}
|
|
@ -32,7 +32,6 @@ fieldset > header,
|
|||
%healthcheck-output dt,
|
||||
%table th,
|
||||
%table td strong,
|
||||
%tooltip-bubble,
|
||||
%sliding-toggle label span {
|
||||
@extend %h6;
|
||||
}
|
||||
|
@ -133,8 +132,3 @@ pre code,
|
|||
font-size: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
//Ember Tooltip
|
||||
.ember-tooltip {
|
||||
font-size: $typo-size-800;
|
||||
}
|
||||
|
|
|
@ -113,6 +113,7 @@
|
|||
"ember-load-initializers": "^2.1.1",
|
||||
"ember-math-helpers": "^2.4.0",
|
||||
"ember-maybe-import-regenerator": "^0.1.6",
|
||||
"ember-modifier": "^2.1.1",
|
||||
"ember-named-blocks-polyfill": "^0.2.3",
|
||||
"ember-on-helper": "^0.1.0",
|
||||
"ember-page-title": "^5.2.3",
|
||||
|
@ -150,6 +151,7 @@
|
|||
"sass": "^1.28.0",
|
||||
"tape": "^5.0.1",
|
||||
"text-encoding": "^0.7.0",
|
||||
"tippy.js": "^6.2.7",
|
||||
"torii": "^0.10.1"
|
||||
},
|
||||
"resolutions": {
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | tooltip', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function(assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`<Tooltip />`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), '');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<Tooltip>
|
||||
</Tooltip>
|
||||
`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), '');
|
||||
});
|
||||
});
|
35
ui/yarn.lock
35
ui/yarn.lock
|
@ -1683,6 +1683,11 @@
|
|||
dependencies:
|
||||
mkdirp "^1.0.4"
|
||||
|
||||
"@popperjs/core@^2.4.4":
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.5.4.tgz#de25b5da9f727985a3757fd59b5d028aba75841a"
|
||||
integrity sha512-ZpKr+WTb8zsajqgDkvCEWgp6d5eJT6Q63Ng2neTbzBO76Lbe91vX/iVIW9dikq+Fs3yEo+ls4cxeXABD2LtcbQ==
|
||||
|
||||
"@reach/router@^1.3.3":
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c"
|
||||
|
@ -7995,6 +8000,15 @@ ember-data@~3.20.4:
|
|||
ember-cli-typescript "^3.1.3"
|
||||
ember-inflector "^3.0.1"
|
||||
|
||||
ember-destroyable-polyfill@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ember-destroyable-polyfill/-/ember-destroyable-polyfill-2.0.2.tgz#2cc7532bd3c00e351b4da9b7fc683f4daff79671"
|
||||
integrity sha512-9t+ya+9c+FkNM5IAyJIv6ETG8jfZQaUnFCO5SeLlV0wkSw7TOexyb61jh5GVee0KmknfRhrRGGAyT4Y0TwkZ+w==
|
||||
dependencies:
|
||||
ember-cli-babel "^7.22.1"
|
||||
ember-cli-version-checker "^5.1.1"
|
||||
ember-compatibility-helpers "^1.2.1"
|
||||
|
||||
ember-element-helper@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-element-helper/-/ember-element-helper-0.2.0.tgz#eacdf4d8507d6708812623206e24ad37bad487e7"
|
||||
|
@ -8108,7 +8122,7 @@ ember-maybe-in-element@^2.0.1:
|
|||
ember-cli-version-checker "^5.1.1"
|
||||
ember-in-element-polyfill "^1.0.0"
|
||||
|
||||
ember-modifier-manager-polyfill@^1.1.0:
|
||||
ember-modifier-manager-polyfill@^1.1.0, ember-modifier-manager-polyfill@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-modifier-manager-polyfill/-/ember-modifier-manager-polyfill-1.2.0.tgz#cf4444e11a42ac84f5c8badd85e635df57565dda"
|
||||
integrity sha512-bnaKF1LLKMkBNeDoetvIJ4vhwRPKIIumWr6dbVuW6W6p4QV8ZiO+GdF8J7mxDNlog9CeL9Z/7wam4YS86G8BYA==
|
||||
|
@ -8117,6 +8131,18 @@ ember-modifier-manager-polyfill@^1.1.0:
|
|||
ember-cli-version-checker "^2.1.2"
|
||||
ember-compatibility-helpers "^1.2.0"
|
||||
|
||||
ember-modifier@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ember-modifier/-/ember-modifier-2.1.1.tgz#aa3a12e2d6cf1622f774f3f1eab4880982a43fa9"
|
||||
integrity sha512-g9mcpFWgw5lgNU40YNf0USNWqoGTJ+EqjDQKjm7556gaRNDeGnLylFKqx9O3opwLHEt6ZODnRDy9U0S5YEMREg==
|
||||
dependencies:
|
||||
ember-cli-babel "^7.22.1"
|
||||
ember-cli-normalize-entity-name "^1.0.0"
|
||||
ember-cli-string-utils "^1.1.0"
|
||||
ember-cli-typescript "^3.1.3"
|
||||
ember-destroyable-polyfill "^2.0.2"
|
||||
ember-modifier-manager-polyfill "^1.2.0"
|
||||
|
||||
ember-named-blocks-polyfill@^0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/ember-named-blocks-polyfill/-/ember-named-blocks-polyfill-0.2.3.tgz#05fb3b40cff98a0d30e8c3b1e3d2155951007d84"
|
||||
|
@ -16135,6 +16161,13 @@ tinycolor2@^1.4.1:
|
|||
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
|
||||
integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==
|
||||
|
||||
tippy.js@^6.2.7:
|
||||
version "6.2.7"
|
||||
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.2.7.tgz#62fb34eda23f7d78151ddca922b62818c1ab9869"
|
||||
integrity sha512-k+kWF9AJz5xLQHBi3K/XlmJiyu+p9gsCyc5qZhxxGaJWIW8SMjw1R+C7saUnP33IM8gUhDA2xX//ejRSwqR0tA==
|
||||
dependencies:
|
||||
"@popperjs/core" "^2.4.4"
|
||||
|
||||
tmp@0.0.28:
|
||||
version "0.0.28"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120"
|
||||
|
|
Loading…
Reference in New Issue