diff --git a/.storybook/preview.js b/.storybook/preview.js
index 07625945de..16e56a0330 100644
--- a/.storybook/preview.js
+++ b/.storybook/preview.js
@@ -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: {
diff --git a/package.json b/package.json
index ea443c3c92..1fdba36239 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/directives/focus.ts b/src/directives/focus.ts
deleted file mode 100644
index fb7310fa9f..0000000000
--- a/src/directives/focus.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import Vue, { DirectiveOptions, DirectiveFunction } from 'vue';
-
-export const definition: DirectiveOptions = {
- inserted(el) {
- el.focus();
- }
-};
-
-Vue.directive('focus', definition);
diff --git a/src/directives/focus/focus.readme.md b/src/directives/focus/focus.readme.md
new file mode 100644
index 0000000000..659125bcbb
--- /dev/null
+++ b/src/directives/focus/focus.readme.md
@@ -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
+
+```
diff --git a/src/directives/focus/focus.story.ts b/src/directives/focus/focus.story.ts
new file mode 100644
index 0000000000..f43348308f
--- /dev/null
+++ b/src/directives/focus/focus.story.ts
@@ -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: `
+
+
+
+
+
+ `
+});
diff --git a/src/directives/focus.test.ts b/src/directives/focus/focus.test.ts
similarity index 73%
rename from src/directives/focus.test.ts
rename to src/directives/focus/focus.test.ts
index 31dbd2824e..d31034d49a 100644
--- a/src/directives/focus.test.ts
+++ b/src/directives/focus/focus.test.ts
@@ -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();
});
});
diff --git a/src/directives/focus/focus.ts b/src/directives/focus/focus.ts
new file mode 100644
index 0000000000..d869fccb8c
--- /dev/null
+++ b/src/directives/focus/focus.ts
@@ -0,0 +1,9 @@
+import { DirectiveOptions } from 'vue';
+
+const Focus: DirectiveOptions = {
+ inserted(el) {
+ el.focus();
+ }
+};
+
+export default Focus;
diff --git a/src/directives/index.ts b/src/directives/index.ts
deleted file mode 100644
index 8ec6657d6b..0000000000
--- a/src/directives/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-import './focus';
-import './tooltip';
diff --git a/src/directives/register.ts b/src/directives/register.ts
new file mode 100644
index 0000000000..2b31a98820
--- /dev/null
+++ b/src/directives/register.ts
@@ -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);
diff --git a/src/directives/tooltip.ts b/src/directives/tooltip.ts
deleted file mode 100644
index ec10f3e9de..0000000000
--- a/src/directives/tooltip.ts
+++ /dev/null
@@ -1 +0,0 @@
-console.log('hi');
diff --git a/src/directives/tooltip/tooltip.readme.md b/src/directives/tooltip/tooltip.readme.md
new file mode 100644
index 0000000000..69657eba6e
--- /dev/null
+++ b/src/directives/tooltip/tooltip.readme.md
@@ -0,0 +1,21 @@
+# Tooltip
+
+The tooltip can display more detailed information about an element to clarify its use.
+
+```html
+This is a 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 |
\ No newline at end of file
diff --git a/src/directives/tooltip/tooltip.story.ts b/src/directives/tooltip/tooltip.story.ts
new file mode 100644
index 0000000000..f7f933e0a1
--- /dev/null
+++ b/src/directives/tooltip/tooltip.story.ts
@@ -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: `
+
+
+ Bottom start
+ Bottom
+ Bottom end
+
+
+ Left start
+ Left
+ Left end
+
+
+ Right start
+ Right
+ Right end
+
+
+ Top start
+ Top
+ Top end
+
+
Instant Tooltip
+
Inverted Tooltip
+
+
+
+
+ `
+});
diff --git a/src/directives/tooltip/tooltip.test.ts b/src/directives/tooltip/tooltip.test.ts
new file mode 100644
index 0000000000..46e522076c
--- /dev/null
+++ b/src/directives/tooltip/tooltip.test.ts
@@ -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');
+ });
+ });
+});
diff --git a/src/directives/tooltip/tooltip.ts b/src/directives/tooltip/tooltip.ts
new file mode 100644
index 0000000000..e337935660
--- /dev/null
+++ b/src/directives/tooltip/tooltip.ts
@@ -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;
+}
diff --git a/src/main.ts b/src/main.ts
index d50b101d25..185e9593cd 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -2,6 +2,7 @@ import Vue from 'vue';
import './styles/main.scss';
import './plugins';
+import './directives/register';
import './components/register';
import './modules/register';
diff --git a/src/plugins.ts b/src/plugins.ts
index ebf2b114eb..06b1b4ccbd 100644
--- a/src/plugins.ts
+++ b/src/plugins.ts
@@ -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);
diff --git a/src/styles/_tooltip.scss b/src/styles/_tooltip.scss
new file mode 100644
index 0000000000..88d4ff9be3
--- /dev/null
+++ b/src/styles/_tooltip.scss
@@ -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;
+ }
+}
diff --git a/src/styles/lib/_tooltip.scss b/src/styles/lib/_tooltip.scss
deleted file mode 100644
index b3bfbfde29..0000000000
--- a/src/styles/lib/_tooltip.scss
+++ /dev/null
@@ -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);
- }
-}
diff --git a/src/styles/main.scss b/src/styles/main.scss
index 3ba6306d0b..289ccd1e1a 100644
--- a/src/styles/main.scss
+++ b/src/styles/main.scss
@@ -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';
diff --git a/src/styles/themes/_default.scss b/src/styles/themes/_default.scss
index 23e30bdfac..f606e320db 100644
--- a/src/styles/themes/_default.scss
+++ b/src/styles/themes/_default.scss
@@ -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);
}
diff --git a/src/views/public/public-view.test.ts b/src/views/public/public-view.test.ts
index 0349806a58..886954fb58 100644
--- a/src/views/public/public-view.test.ts
+++ b/src/views/public/public-view.test.ts
@@ -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';
diff --git a/yarn.lock b/yarn.lock
index 3680b11443..844a5a5106 100644
--- a/yarn.lock
+++ b/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"