mirror of
https://github.com/directus/directus.git
synced 2026-02-07 19:05:00 -05:00
Custom Tooltip (#43)
* implemented basic tooltip * add animation * finish up tooltip, added instant option * implemented basic tooltip * add animation * finish up tooltip, added instant option * Uninstall v-tooltip * Match folder structure of focus to tooltip * Register new directives * remove duplicate folder * Export functions, cleanup animate * Export update tooltip function * Increase test covergae * Added start and end options * Structure positioning tests * tooltip right end will now show on the right end * Add tests for modifier states * Update test coverage * Fix stylelint issues * made top as default position * added inverted option * fix lint * Move tooltip style vars to theme * Remove line clamp * Update tests Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
This commit is contained in:
@@ -3,6 +3,8 @@ import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
|
||||
|
||||
import "../src/styles/main.scss";
|
||||
import "../src/plugins";
|
||||
import "../src/components/register";
|
||||
import "../src/directives/register";
|
||||
|
||||
addParameters({
|
||||
docs: {
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"lodash": "^4.17.15",
|
||||
"nanoid": "^2.1.11",
|
||||
"pinia": "0.0.5",
|
||||
"v-tooltip": "^2.0.3",
|
||||
"vue": "^2.6.11",
|
||||
"vue-i18n": "^8.15.3",
|
||||
"vue-router": "^3.1.5",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import Vue, { DirectiveOptions, DirectiveFunction } from 'vue';
|
||||
|
||||
export const definition: DirectiveOptions = {
|
||||
inserted(el) {
|
||||
el.focus();
|
||||
}
|
||||
};
|
||||
|
||||
Vue.directive('focus', definition);
|
||||
7
src/directives/focus/focus.readme.md
Normal file
7
src/directives/focus/focus.readme.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Focus
|
||||
|
||||
The focus directive is basically `autofocus`, but works in Vue. Because of the way HTML works, `autofocus` isn't triggered for DOM elements that are added after first load, which means that `autofocus` basically never works in the context of the Directus app. That's where you can use `v-focus` instead:
|
||||
|
||||
```html
|
||||
<v-input v-focus />
|
||||
```
|
||||
27
src/directives/focus/focus.story.ts
Normal file
27
src/directives/focus/focus.story.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import Vue from 'vue';
|
||||
import VInput from '../../components/v-input';
|
||||
import Focus from './focus';
|
||||
import markdown from './focus.readme.md';
|
||||
import withPadding from '../../../.storybook/decorators/with-padding';
|
||||
|
||||
Vue.component('v-input', VInput);
|
||||
Vue.directive('focus', Focus);
|
||||
|
||||
export default {
|
||||
title: 'Directives / Focus',
|
||||
component: VInput,
|
||||
decorators: [withPadding],
|
||||
parameters: {
|
||||
notes: markdown
|
||||
}
|
||||
};
|
||||
|
||||
export const withText = () => ({
|
||||
template: `
|
||||
<div style="display: flex; justify-content: space-around; flex-direction: column; align-items: center">
|
||||
<v-input style="margin-bottom: 20px;" />
|
||||
<v-input style="margin-bottom: 20px;" />
|
||||
<v-input v-focus style="margin-bottom: 20px;" />
|
||||
</div>
|
||||
`
|
||||
});
|
||||
@@ -1,11 +1,11 @@
|
||||
import { definition } from './focus';
|
||||
import Focus from './focus';
|
||||
|
||||
describe('Directives / Focus', () => {
|
||||
it('Calls focus() on the element on insertion', () => {
|
||||
const el = { focus: jest.fn() };
|
||||
// I don't care about the exact types of this Vue internal function. We just want to make
|
||||
// sure `focus()` is being called on `el`.
|
||||
definition.inserted!(el as any, null as any, null as any, null as any);
|
||||
Focus.inserted!(el as any, null as any, null as any, null as any);
|
||||
expect(el.focus).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
9
src/directives/focus/focus.ts
Normal file
9
src/directives/focus/focus.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { DirectiveOptions } from 'vue';
|
||||
|
||||
const Focus: DirectiveOptions = {
|
||||
inserted(el) {
|
||||
el.focus();
|
||||
}
|
||||
};
|
||||
|
||||
export default Focus;
|
||||
@@ -1,2 +0,0 @@
|
||||
import './focus';
|
||||
import './tooltip';
|
||||
7
src/directives/register.ts
Normal file
7
src/directives/register.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
import Focus from './focus/focus';
|
||||
import Tooltip from './tooltip/tooltip';
|
||||
|
||||
Vue.directive('focus', Focus);
|
||||
Vue.directive('tooltip', Tooltip);
|
||||
@@ -1 +0,0 @@
|
||||
console.log('hi');
|
||||
21
src/directives/tooltip/tooltip.readme.md
Normal file
21
src/directives/tooltip/tooltip.readme.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Tooltip
|
||||
|
||||
The tooltip can display more detailed information about an element to clarify its use.
|
||||
|
||||
```html
|
||||
<v-button v-tooltip="it does absolutely nothing">This is a button</v-button>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
The tooltip is displayed at the bottom of an element by default.
|
||||
|
||||
| Option | Description |
|
||||
|-----------|-------------------------------------------------|
|
||||
| `left` | Display the tooltip to the left of the element |
|
||||
| `right` | Display the tooltip to the right of the element |
|
||||
| `bottom` | Display the tooltip on bottom of the element |
|
||||
| `start` | Display the tooltip to the end of the element |
|
||||
| `end` | Display the tooltip to the start of the element |
|
||||
| `instant` | Shows the tooltip instantly on hover |
|
||||
| `inverted`| Inverts all colors |
|
||||
57
src/directives/tooltip/tooltip.story.ts
Normal file
57
src/directives/tooltip/tooltip.story.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
withKnobs,
|
||||
text,
|
||||
boolean,
|
||||
number,
|
||||
color,
|
||||
optionsKnob as options
|
||||
} from '@storybook/addon-knobs';
|
||||
import Vue from 'vue';
|
||||
import VButton from '../../components/v-button';
|
||||
import VIcon from '../../components/v-icon';
|
||||
import markdown from './tooltip.readme.md';
|
||||
import withPadding from '../../../.storybook/decorators/with-padding';
|
||||
|
||||
Vue.component('v-button', VButton);
|
||||
Vue.component('v-icon', VIcon);
|
||||
|
||||
export default {
|
||||
title: 'Directives / Tooltip',
|
||||
component: VButton,
|
||||
decorators: [withKnobs, withPadding],
|
||||
parameters: {
|
||||
notes: markdown
|
||||
}
|
||||
};
|
||||
|
||||
export const withText = () => ({
|
||||
template: `
|
||||
<div style="display: flex; justify-content: space-around; flex-direction: column; align-items: center">
|
||||
<div>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.bottom.start="'Default Tooltip'">Bottom start</v-button>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.bottom="'Default Tooltip'">Bottom</v-button>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.bottom.end="'Default Tooltip'">Bottom end</v-button>
|
||||
</div>
|
||||
<div>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.left.start="'Default Tooltip'">Left start</v-button>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.left="'Default Tooltip'">Left</v-button>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.left.end="'Default Tooltip'">Left end</v-button>
|
||||
</div>
|
||||
<div>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.right.start="'Default Tooltip'">Right start</v-button>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.right="'Default Tooltip'">Right</v-button>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.right.end="'Default Tooltip'">Right end</v-button>
|
||||
</div>
|
||||
<div>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.start="'Default Tooltip'">Top start</v-button>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip="'Default Tooltip'">Top</v-button>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.end="'Default Tooltip'">Top end</v-button>
|
||||
</div>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.instant="'Tooltip'">Instant Tooltip</v-button>
|
||||
<v-button style="width: 150px; margin: 30px" v-tooltip.inverted="'Tooltip'">Inverted Tooltip</v-button>
|
||||
<v-icon style="margin: 30px" v-tooltip.top="'Goto home menu'" name="home" />
|
||||
<v-icon style="margin: 30px" v-tooltip.bottom="'This is a really long text which wont fit into a single line!'" name="info" />
|
||||
<v-icon style="margin: 30px" v-tooltip.right="'This is a really long text which wont fit into a single line!'" name="info" />
|
||||
</div>
|
||||
`
|
||||
});
|
||||
357
src/directives/tooltip/tooltip.test.ts
Normal file
357
src/directives/tooltip/tooltip.test.ts
Normal file
@@ -0,0 +1,357 @@
|
||||
import { createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VButton from '../../components/v-button';
|
||||
import Tooltip, {
|
||||
getTooltip,
|
||||
animateIn,
|
||||
animateOut,
|
||||
onLeaveTooltip,
|
||||
updateTooltip,
|
||||
onEnterTooltip
|
||||
} from './tooltip';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-button', VButton);
|
||||
localVue.directive('tooltip', Tooltip);
|
||||
|
||||
describe('Tooltip', () => {
|
||||
afterEach(() => {
|
||||
document.getElementsByTagName('html')[0].innerHTML = '';
|
||||
});
|
||||
|
||||
describe('onEnterTooltip', () => {
|
||||
it('Instant does not wait to show the tooltip', () => {
|
||||
const div = document.createElement('div');
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.id = 'tooltip';
|
||||
document.body.appendChild(tooltip);
|
||||
|
||||
onEnterTooltip(div, {
|
||||
name: 'tooltip',
|
||||
modifiers: {
|
||||
top: true,
|
||||
instant: true
|
||||
}
|
||||
});
|
||||
|
||||
expect(tooltip.className).toBe('visible enter top');
|
||||
});
|
||||
|
||||
it('Default waits 600ms to show tooltip', () => {
|
||||
const div = document.createElement('div');
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.id = 'tooltip';
|
||||
document.body.appendChild(tooltip);
|
||||
|
||||
onEnterTooltip(div, {
|
||||
name: 'tooltip',
|
||||
modifiers: {
|
||||
top: true,
|
||||
instant: false
|
||||
}
|
||||
});
|
||||
|
||||
expect(tooltip.className).toBe('');
|
||||
jest.advanceTimersByTime(650);
|
||||
expect(tooltip.className).toBe('visible top enter-active');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateTooltip', () => {
|
||||
describe('Styles and classes', () => {
|
||||
type Modifier = {
|
||||
right: boolean;
|
||||
bottom: boolean;
|
||||
left: boolean;
|
||||
start: boolean;
|
||||
end: boolean;
|
||||
inverted: boolean;
|
||||
};
|
||||
|
||||
function testUpdateTooltip(modifiers: Modifier) {
|
||||
const div = document.createElement('div');
|
||||
const tooltip = document.createElement('div');
|
||||
updateTooltip(
|
||||
div,
|
||||
{
|
||||
name: 'tooltip',
|
||||
modifiers: modifiers
|
||||
},
|
||||
tooltip
|
||||
);
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
test('top (default)', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
start: false,
|
||||
end: false,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('top');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(calc(0px - 50%), calc(-10px - 100%));'
|
||||
);
|
||||
});
|
||||
|
||||
test('top start', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
start: true,
|
||||
end: false,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('start top');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(calc(20px - 100%), calc(-10px - 100%));'
|
||||
);
|
||||
});
|
||||
|
||||
test('top end', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
start: false,
|
||||
end: true,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('end top');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(calc(-20px - 0%), calc(-10px - 100%));'
|
||||
);
|
||||
});
|
||||
|
||||
test('right', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: true,
|
||||
bottom: false,
|
||||
left: false,
|
||||
start: false,
|
||||
end: false,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('right');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(10px, calc(0px - 50%));'
|
||||
);
|
||||
});
|
||||
|
||||
test('right start', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: true,
|
||||
bottom: false,
|
||||
left: false,
|
||||
start: true,
|
||||
end: false,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('start right');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(10px, calc(20px - 100%));'
|
||||
);
|
||||
});
|
||||
|
||||
test('right end', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: true,
|
||||
bottom: false,
|
||||
left: false,
|
||||
start: false,
|
||||
end: true,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('end right');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(10px, calc(-20px - 0%));'
|
||||
);
|
||||
});
|
||||
|
||||
test('bottom', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: false,
|
||||
bottom: true,
|
||||
left: false,
|
||||
start: false,
|
||||
end: false,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('bottom');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(calc(0px - 50%), 10px);'
|
||||
);
|
||||
});
|
||||
|
||||
test('bottom start', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: false,
|
||||
bottom: true,
|
||||
left: false,
|
||||
start: true,
|
||||
end: false,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('start bottom');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(calc(20px - 100%), 10px);'
|
||||
);
|
||||
});
|
||||
|
||||
test('bottom end', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: false,
|
||||
bottom: true,
|
||||
left: false,
|
||||
start: false,
|
||||
end: true,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('end bottom');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(calc(-20px - 0%), 10px);'
|
||||
);
|
||||
});
|
||||
|
||||
test('left', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: true,
|
||||
start: false,
|
||||
end: false,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('left');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(calc(-10px - 100%), calc(0px - 50%));'
|
||||
);
|
||||
});
|
||||
|
||||
test('left start', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: true,
|
||||
start: true,
|
||||
end: false,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('start left');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(calc(-10px - 100%), calc(20px - 100%));'
|
||||
);
|
||||
});
|
||||
|
||||
test('left end', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: true,
|
||||
start: false,
|
||||
end: true,
|
||||
inverted: false
|
||||
});
|
||||
expect(tooltip.className).toBe('end left');
|
||||
expect(tooltip.getAttribute('style')).toBe(
|
||||
'transform: translate(calc(-10px - 100%), calc(-20px - 0%));'
|
||||
);
|
||||
});
|
||||
|
||||
test('Inverted', () => {
|
||||
const tooltip = testUpdateTooltip({
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
start: false,
|
||||
end: false,
|
||||
inverted: true
|
||||
});
|
||||
expect(tooltip.className).toBe('inverted top');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onLeaveTooltip', () => {
|
||||
it('Clears the timeout', () => {
|
||||
onLeaveTooltip();
|
||||
expect(clearTimeout).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('animateIn / animateOut', () => {
|
||||
it('Adds the appropriate classes on entering', () => {
|
||||
const div = document.createElement('div');
|
||||
animateIn(div);
|
||||
|
||||
expect(div.classList).toContain('enter');
|
||||
jest.advanceTimersByTime(5);
|
||||
expect(div.classList).toContain('enter-active');
|
||||
jest.advanceTimersByTime(225);
|
||||
expect(div.classList.contains('enter-active')).toBe(false);
|
||||
});
|
||||
|
||||
it('Stops animating in when it already has enter / enter-active class', () => {
|
||||
const tooltip = document.createElement('div');
|
||||
|
||||
animateIn(tooltip);
|
||||
tooltip.classList.remove('enter');
|
||||
|
||||
jest.advanceTimersByTime(5);
|
||||
expect(tooltip.classList.contains('enter-active')).toBe(false);
|
||||
jest.advanceTimersByTime(225);
|
||||
expect(tooltip.classList.contains('enter-active')).toBe(false);
|
||||
});
|
||||
|
||||
it('Adds the appropriate classes on leave', () => {
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('visible');
|
||||
|
||||
animateOut(div);
|
||||
|
||||
expect(div.classList).toContain('leave');
|
||||
jest.advanceTimersByTime(5);
|
||||
expect(div.classList).toContain('leave-active');
|
||||
jest.advanceTimersByTime(225);
|
||||
expect(div.classList.contains('leave-active')).toBe(false);
|
||||
});
|
||||
|
||||
it('Stops animating out when it does not have leave / leave-active class', () => {
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.classList.add('visible');
|
||||
|
||||
animateOut(tooltip);
|
||||
tooltip.classList.remove('leave');
|
||||
|
||||
jest.advanceTimersByTime(5);
|
||||
expect(tooltip.classList.contains('leave-active')).toBe(false);
|
||||
jest.advanceTimersByTime(225);
|
||||
expect(tooltip.classList.contains('visible')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTooltip', () => {
|
||||
it('Creates a new div element if tooltip does not exist yet', () => {
|
||||
const spy = jest.spyOn(document, 'createElement');
|
||||
getTooltip();
|
||||
expect(spy).toHaveBeenCalledWith('div');
|
||||
});
|
||||
|
||||
it('Returns the existing div if found in dom', () => {
|
||||
const div = document.createElement('div');
|
||||
div.id = 'tooltip';
|
||||
div.setAttribute('test', 'true');
|
||||
document.body.appendChild(div);
|
||||
|
||||
const returnedDiv = getTooltip();
|
||||
expect(returnedDiv.getAttribute('test')).toBe('true');
|
||||
});
|
||||
});
|
||||
});
|
||||
175
src/directives/tooltip/tooltip.ts
Normal file
175
src/directives/tooltip/tooltip.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { DirectiveOptions } from 'vue';
|
||||
import { DirectiveBinding } from 'vue/types/options';
|
||||
|
||||
const Tooltip: DirectiveOptions = {
|
||||
inserted(element, binding) {
|
||||
element.onmouseenter = () => onEnterTooltip(element, binding);
|
||||
element.onmouseleave = () => onLeaveTooltip();
|
||||
}
|
||||
};
|
||||
|
||||
export default Tooltip;
|
||||
|
||||
let tooltipTimer: number;
|
||||
|
||||
export function onEnterTooltip(element: HTMLElement, binding: DirectiveBinding) {
|
||||
const tooltip = getTooltip();
|
||||
|
||||
if (binding.modifiers.instant) {
|
||||
animateIn(tooltip);
|
||||
updateTooltip(element, binding, tooltip);
|
||||
} else {
|
||||
tooltipTimer = setTimeout(() => {
|
||||
animateIn(tooltip);
|
||||
updateTooltip(element, binding, tooltip);
|
||||
}, 600);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateTooltip(
|
||||
element: HTMLElement,
|
||||
binding: DirectiveBinding,
|
||||
tooltip: HTMLElement
|
||||
) {
|
||||
const offset = 10;
|
||||
const arrowAlign = 20;
|
||||
|
||||
const bounds = element.getBoundingClientRect();
|
||||
let top = bounds.top + pageYOffset;
|
||||
let left = bounds.left + pageXOffset;
|
||||
let transformPos;
|
||||
|
||||
tooltip.innerText = binding.value;
|
||||
tooltip.classList.remove('top', 'bottom', 'left', 'right', 'start', 'end');
|
||||
|
||||
if (binding.modifiers.inverted) {
|
||||
tooltip.classList.add('inverted');
|
||||
} else {
|
||||
tooltip.classList.remove('inverted');
|
||||
}
|
||||
|
||||
if (binding.modifiers.bottom) {
|
||||
if (binding.modifiers.start) {
|
||||
left += arrowAlign;
|
||||
transformPos = 100;
|
||||
tooltip.classList.add('start');
|
||||
} else if (binding.modifiers.end) {
|
||||
left += bounds.width - arrowAlign;
|
||||
transformPos = 0;
|
||||
tooltip.classList.add('end');
|
||||
} else {
|
||||
left += bounds.width / 2;
|
||||
transformPos = 50;
|
||||
}
|
||||
|
||||
top += bounds.height + offset;
|
||||
tooltip.style.transform = `translate(calc(${left}px - ${transformPos}%), ${top}px)`;
|
||||
tooltip.classList.add('bottom');
|
||||
} else if (binding.modifiers.left) {
|
||||
if (binding.modifiers.start) {
|
||||
top += arrowAlign;
|
||||
transformPos = 100;
|
||||
tooltip.classList.add('start');
|
||||
} else if (binding.modifiers.end) {
|
||||
top += bounds.height - arrowAlign;
|
||||
transformPos = 0;
|
||||
tooltip.classList.add('end');
|
||||
} else {
|
||||
top += bounds.height / 2;
|
||||
transformPos = 50;
|
||||
}
|
||||
|
||||
left -= offset;
|
||||
tooltip.style.transform = `translate(calc(${left}px - 100%), calc(${top}px - ${transformPos}%))`;
|
||||
tooltip.classList.add('left');
|
||||
} else if (binding.modifiers.right) {
|
||||
if (binding.modifiers.start) {
|
||||
top += arrowAlign;
|
||||
transformPos = 100;
|
||||
tooltip.classList.add('start');
|
||||
} else if (binding.modifiers.end) {
|
||||
top += bounds.height - arrowAlign;
|
||||
transformPos = 0;
|
||||
tooltip.classList.add('end');
|
||||
} else {
|
||||
top += bounds.height / 2;
|
||||
transformPos = 50;
|
||||
}
|
||||
|
||||
left += bounds.width + offset;
|
||||
tooltip.style.transform = `translate(${left}px, calc(${top}px - ${transformPos}%))`;
|
||||
tooltip.classList.add('right');
|
||||
} else {
|
||||
if (binding.modifiers.start) {
|
||||
left += arrowAlign;
|
||||
transformPos = 100;
|
||||
tooltip.classList.add('start');
|
||||
} else if (binding.modifiers.end) {
|
||||
left += bounds.width - arrowAlign;
|
||||
transformPos = 0;
|
||||
tooltip.classList.add('end');
|
||||
} else {
|
||||
left += bounds.width / 2;
|
||||
transformPos = 50;
|
||||
}
|
||||
|
||||
top -= offset;
|
||||
tooltip.style.transform = `translate(calc(${left}px - ${transformPos}%), calc(${top}px - 100%))`;
|
||||
tooltip.classList.add('top');
|
||||
}
|
||||
}
|
||||
|
||||
export function onLeaveTooltip() {
|
||||
const tooltip = getTooltip();
|
||||
|
||||
clearTimeout(tooltipTimer);
|
||||
animateOut(tooltip);
|
||||
}
|
||||
|
||||
export function animateIn(tooltip: HTMLElement) {
|
||||
tooltip.classList.add('visible', 'enter');
|
||||
tooltip.classList.remove('leave', 'leave-active');
|
||||
|
||||
setTimeout(() => {
|
||||
if (tooltip.classList.contains('enter') === false) return;
|
||||
tooltip.classList.add('enter-active');
|
||||
tooltip.classList.remove('enter');
|
||||
}, 1);
|
||||
|
||||
setTimeout(() => {
|
||||
tooltip.classList.remove('enter-active');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
export function animateOut(tooltip: HTMLElement) {
|
||||
if (tooltip.classList.contains('visible') === false) return;
|
||||
|
||||
tooltip.classList.add('visible', 'leave');
|
||||
tooltip.classList.remove('enter', 'enter-active');
|
||||
|
||||
setTimeout(() => {
|
||||
if (tooltip.classList.contains('leave') === false) return;
|
||||
tooltip.classList.add('leave-active');
|
||||
tooltip.classList.remove('leave');
|
||||
}, 1);
|
||||
|
||||
setTimeout(() => {
|
||||
if (tooltip.classList.contains('leave-active') === false) return;
|
||||
tooltip.classList.remove('leave-active');
|
||||
tooltip.classList.remove('visible');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
export function getTooltip(): HTMLElement {
|
||||
let tooltip = document.getElementById('tooltip');
|
||||
|
||||
if (tooltip instanceof HTMLElement) {
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
tooltip = document.createElement('div');
|
||||
tooltip.id = 'tooltip';
|
||||
document.body.appendChild(tooltip);
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import Vue from 'vue';
|
||||
|
||||
import './styles/main.scss';
|
||||
import './plugins';
|
||||
import './directives/register';
|
||||
import './components/register';
|
||||
import './modules/register';
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import VueRouter from 'vue-router';
|
||||
import { VTooltip } from 'v-tooltip';
|
||||
|
||||
Vue.use(VueCompositionAPI);
|
||||
Vue.use(VueRouter);
|
||||
Vue.directive('tooltip', VTooltip);
|
||||
|
||||
110
src/styles/_tooltip.scss
Normal file
110
src/styles/_tooltip.scss
Normal file
@@ -0,0 +1,110 @@
|
||||
#tooltip {
|
||||
$arrow-alignment: 5px;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: none;
|
||||
max-width: 260px;
|
||||
padding: 4px 8px;
|
||||
color: var(--tooltip-foreground-color);
|
||||
background-color: var(--tooltip-background-color);
|
||||
border-radius: 4px;
|
||||
transition: opacity 200ms;
|
||||
|
||||
&.inverted {
|
||||
--tooltip-foreground-color: var(--tooltip-foreground-color-inverted);
|
||||
--tooltip-background-color: var(--tooltip-background-color-inverted);
|
||||
}
|
||||
|
||||
&.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.enter,
|
||||
&.leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.enter-active,
|
||||
&.leave {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: calc(50% - 5px);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-right: 5px solid transparent;
|
||||
border-bottom: 5px solid var(--tooltip-background-color);
|
||||
border-left: 5px solid transparent;
|
||||
content: '';
|
||||
}
|
||||
|
||||
&.start::after {
|
||||
right: $arrow-alignment;
|
||||
left: unset;
|
||||
}
|
||||
|
||||
&.end::after {
|
||||
left: $arrow-alignment;
|
||||
}
|
||||
|
||||
&.top::after {
|
||||
top: unset;
|
||||
bottom: -5px;
|
||||
left: calc(50% - 5px);
|
||||
border-top: 5px solid var(--tooltip-background-color);
|
||||
border-right: 5px solid transparent;
|
||||
border-bottom: unset;
|
||||
border-left: 5px solid transparent;
|
||||
}
|
||||
|
||||
&.top.start::after {
|
||||
right: $arrow-alignment;
|
||||
left: unset;
|
||||
}
|
||||
|
||||
&.top.end::after {
|
||||
left: $arrow-alignment;
|
||||
}
|
||||
|
||||
&.left::after {
|
||||
top: calc(50% - 5px);
|
||||
right: -5px;
|
||||
left: unset;
|
||||
border-top: 5px solid transparent;
|
||||
border-right: unset;
|
||||
border-bottom: 5px solid transparent;
|
||||
border-left: 5px solid var(--tooltip-background-color);
|
||||
}
|
||||
|
||||
&.left.start::after {
|
||||
top: unset;
|
||||
bottom: $arrow-alignment;
|
||||
}
|
||||
|
||||
&.left.end::after {
|
||||
top: $arrow-alignment;
|
||||
}
|
||||
|
||||
&.right::after {
|
||||
top: calc(50% - 5px);
|
||||
left: -5px;
|
||||
border-top: 5px solid transparent;
|
||||
border-right: 5px solid var(--tooltip-background-color);
|
||||
border-bottom: 5px solid transparent;
|
||||
border-left: unset;
|
||||
}
|
||||
|
||||
&.right.start::after {
|
||||
top: unset;
|
||||
bottom: $arrow-alignment;
|
||||
}
|
||||
|
||||
&.right.end::after {
|
||||
top: $arrow-alignment;
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
.tooltip {
|
||||
z-index: 10000;
|
||||
display: block !important;
|
||||
background: var(--tooltip-background-color);
|
||||
border-radius: 3px;
|
||||
|
||||
& .tooltip-inner {
|
||||
padding: 5px 10px 6px;
|
||||
color: var(--tooltip-text-color);
|
||||
}
|
||||
|
||||
& .tooltip-arrow {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 5px;
|
||||
border-color: var(--tooltip-background-color);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
&.popover-arrow {
|
||||
&::after {
|
||||
border-color: var(--tooltip-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.popover {
|
||||
background: var(--popover-background-color);
|
||||
border: 2px solid var(--input-border-color);
|
||||
}
|
||||
|
||||
& .popover-inner {
|
||||
padding: 6px 0;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
& .popover-arrow {
|
||||
border-color: var(--input-border-color);
|
||||
|
||||
&::after {
|
||||
border-color: var(--popover-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.inverted {
|
||||
background: var(--blue-grey-50);
|
||||
|
||||
& .tooltip-inner {
|
||||
color: var(--blue-grey-800);
|
||||
}
|
||||
|
||||
& .tooltip-arrow {
|
||||
border-color: var(--blue-grey-50);
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^='top'] {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&[x-placement^='top'] .tooltip-arrow {
|
||||
bottom: -4px;
|
||||
left: calc(50% - 4px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-width: 4px 4px 0 4px;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
border-left-color: transparent !important;
|
||||
|
||||
&.popover-arrow {
|
||||
bottom: -8px;
|
||||
left: calc(50% - 8px);
|
||||
border-width: 8px 8px 0 8px;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
left: calc(50% - 8px);
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-style: solid;
|
||||
border-width: 8px 8px 0 8px;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
border-left-color: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^='bottom'] {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
&[x-placement^='bottom'] .tooltip-arrow {
|
||||
top: -4px;
|
||||
left: calc(50% - 4px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-width: 0 4px 4px 4px;
|
||||
border-top-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-left-color: transparent !important;
|
||||
|
||||
&.popover-arrow {
|
||||
top: -8px;
|
||||
left: calc(50% - 8px);
|
||||
border-width: 0 8px 8px 8px;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: calc(50% - 8px);
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 8px 8px 8px;
|
||||
border-top-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-left-color: transparent !important;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^='right'] {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
&[x-placement^='right'] .tooltip-arrow {
|
||||
top: calc(50% - 4px);
|
||||
left: -4px;
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
border-width: 4px 4px 4px 0;
|
||||
border-top-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
border-left-color: transparent !important;
|
||||
|
||||
&.popover-arrow {
|
||||
top: calc(50% - 8px);
|
||||
left: -8px;
|
||||
border-width: 8px 8px 8px 0;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: calc(50% - 8px);
|
||||
left: 3px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
border-style: solid;
|
||||
border-width: 8px 8px 8px 0;
|
||||
border-top-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
border-left-color: transparent !important;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^='left'] {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
&[x-placement^='left'] .tooltip-arrow {
|
||||
top: calc(50% - 4px);
|
||||
right: -4px;
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
border-width: 4px 0 4px 4px;
|
||||
border-top-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
|
||||
&.popover-arrow {
|
||||
top: calc(50% - 8px);
|
||||
right: -8px;
|
||||
border-width: 8px 0 8px 8px;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: calc(50% - 8px);
|
||||
right: 3px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
border-style: solid;
|
||||
border-width: 8px 0 8px 8px;
|
||||
border-top-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[aria-hidden='true'] {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: var(--fast) var(--transition-in);
|
||||
transition-property: opacity, visibility;
|
||||
}
|
||||
|
||||
&[aria-hidden='false'] {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: var(--fast) var(--transition-out);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
@import 'fonts';
|
||||
@import 'type-styles';
|
||||
@import 'variables';
|
||||
@import 'tooltip';
|
||||
@import 'themes/default';
|
||||
@import 'themes/dark-mode';
|
||||
@import 'lib/codemirror';
|
||||
@import 'lib/tooltip';
|
||||
|
||||
@@ -82,4 +82,9 @@ body {
|
||||
*/
|
||||
--overlay-color: rgba(38, 50, 56, 0.75);
|
||||
--divider-color: var(--blue-grey-50);
|
||||
|
||||
--tooltip-foreground-color: var(--button-primary-foreground-color);
|
||||
--tooltip-background-color: var(--button-primary-background-color-hover);
|
||||
--tooltip-foreground-color-inverted: var(--input-foreground-color);
|
||||
--tooltip-background-color-inverted: var(--button-secondary-background-color);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { mount, createLocalVue, Wrapper } from '@vue/test-utils';
|
||||
import { VTooltip } from 'v-tooltip';
|
||||
import VIcon from '@/components/v-icon/';
|
||||
import { useProjectsStore, ProjectWithKey } from '@/stores/projects';
|
||||
import Tooltip from '@/directives/tooltip/tooltip';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.directive('tooltip', VTooltip);
|
||||
localVue.component('v-icon', VIcon);
|
||||
localVue.directive('tooltip', Tooltip);
|
||||
|
||||
import PublicView from './public-view.vue';
|
||||
|
||||
|
||||
16
yarn.lock
16
yarn.lock
@@ -10752,7 +10752,7 @@ polished@^3.3.1:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.3"
|
||||
|
||||
popper.js@^1.14.4, popper.js@^1.14.7, popper.js@^1.16.0:
|
||||
popper.js@^1.14.4, popper.js@^1.14.7:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
|
||||
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
|
||||
@@ -14435,15 +14435,6 @@ uuid@^3.0.1, uuid@^3.3.2:
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
||||
v-tooltip@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.3.tgz#34fd64096656f032b1616567bf62f6165c57d529"
|
||||
integrity sha512-KZZY3s+dcijzZmV2qoDH4rYmjMZ9YKGBVoUznZKQX0e3c2GjpJm3Sldzz8HHH2Ud87JqhZPB4+4gyKZ6m98cKQ==
|
||||
dependencies:
|
||||
lodash "^4.17.15"
|
||||
popper.js "^1.16.0"
|
||||
vue-resize "^0.4.5"
|
||||
|
||||
v8-compile-cache@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
|
||||
@@ -14569,11 +14560,6 @@ vue-loader@^15.7.1, vue-loader@^15.7.2, vue-loader@^15.8.3:
|
||||
vue-hot-reload-api "^2.3.0"
|
||||
vue-style-loader "^4.1.0"
|
||||
|
||||
vue-resize@^0.4.5:
|
||||
version "0.4.5"
|
||||
resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-0.4.5.tgz#4777a23042e3c05620d9cbda01c0b3cc5e32dcea"
|
||||
integrity sha512-bhP7MlgJQ8TIkZJXAfDf78uJO+mEI3CaLABLjv0WNzr4CcGRGPIAItyWYnP6LsPA4Oq0WE+suidNs6dgpO4RHg==
|
||||
|
||||
vue-router@^3.1.3, vue-router@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.1.5.tgz#ff29b8a1e1306c526b52d4dc0532109f16c41231"
|
||||
|
||||
Reference in New Issue
Block a user