mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
feat: add tooltips for better UX (#1554)
This commit is contained in:
parent
64601411d8
commit
b96e072c02
22 changed files with 200 additions and 27 deletions
|
@ -40,6 +40,7 @@
|
||||||
"@babel/polyfill": "^7.8.7",
|
"@babel/polyfill": "^7.8.7",
|
||||||
"@babel/preset-env": "^7.9.6",
|
"@babel/preset-env": "^7.9.6",
|
||||||
"@faker-js/faker": "^6.2.0",
|
"@faker-js/faker": "^6.2.0",
|
||||||
|
"@floating-ui/dom": "^1.0.3",
|
||||||
"@testing-library/cypress": "^8.0.2",
|
"@testing-library/cypress": "^8.0.2",
|
||||||
"@testing-library/vue": "^6.5.1",
|
"@testing-library/vue": "^6.5.1",
|
||||||
"@types/axios": "^0.14.0",
|
"@types/axios": "^0.14.0",
|
||||||
|
|
|
@ -2,7 +2,6 @@ import isMobile from 'ismobilejs'
|
||||||
import { isObject, mergeWith } from 'lodash'
|
import { isObject, mergeWith } from 'lodash'
|
||||||
import { cleanup, render, RenderOptions } from '@testing-library/vue'
|
import { cleanup, render, RenderOptions } from '@testing-library/vue'
|
||||||
import { afterEach, beforeEach, vi } from 'vitest'
|
import { afterEach, beforeEach, vi } from 'vitest'
|
||||||
import { clickaway, focus } from '@/directives'
|
|
||||||
import { defineComponent, nextTick } from 'vue'
|
import { defineComponent, nextTick } from 'vue'
|
||||||
import { commonStore, userStore } from '@/stores'
|
import { commonStore, userStore } from '@/stores'
|
||||||
import { http } from '@/services'
|
import { http } from '@/services'
|
||||||
|
@ -87,8 +86,9 @@ export default abstract class UnitTestCase {
|
||||||
return render(component, deepMerge({
|
return render(component, deepMerge({
|
||||||
global: {
|
global: {
|
||||||
directives: {
|
directives: {
|
||||||
'koel-clickaway': clickaway,
|
'koel-clickaway': {},
|
||||||
'koel-focus': focus
|
'koel-focus': {},
|
||||||
|
'koel-tooltip': {}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
icon: this.stub('icon')
|
icon: this.stub('icon')
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { clickaway, focus } from '@/directives'
|
import { clickaway, focus, tooltip } from '@/directives'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||||
import { RouterKey } from '@/symbols'
|
import { RouterKey } from '@/symbols'
|
||||||
import { routes } from '@/config'
|
import { routes } from '@/config'
|
||||||
|
@ -11,6 +11,7 @@ createApp(App)
|
||||||
.component('icon', FontAwesomeIcon)
|
.component('icon', FontAwesomeIcon)
|
||||||
.directive('koel-focus', focus)
|
.directive('koel-focus', focus)
|
||||||
.directive('koel-clickaway', clickaway)
|
.directive('koel-clickaway', clickaway)
|
||||||
|
.directive('koel-tooltip', tooltip)
|
||||||
/**
|
/**
|
||||||
* For Ancelot, the ancient cross of war
|
* For Ancelot, the ancient cross of war
|
||||||
* for the holy town of Gods
|
* for the holy town of Gods
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="song?.playback_state === 'Playing'"
|
v-if="song?.playback_state === 'Playing'"
|
||||||
|
v-koel-tooltip.top
|
||||||
class="visualizer-btn"
|
class="visualizer-btn"
|
||||||
data-testid="toggle-visualizer-btn"
|
data-testid="toggle-visualizer-btn"
|
||||||
title="Show/hide the visualizer"
|
title="Toggle the visualizer"
|
||||||
type="button"
|
type="button"
|
||||||
@click.prevent="toggleVisualizer"
|
@click.prevent="toggleVisualizer"
|
||||||
>
|
>
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="useEqualizer"
|
v-if="useEqualizer"
|
||||||
|
v-koel-tooltip.top
|
||||||
:class="{ active: showEqualizer }"
|
:class="{ active: showEqualizer }"
|
||||||
:title="`${ showEqualizer ? 'Hide' : 'Show'} equalizer`"
|
:title="`${ showEqualizer ? 'Hide' : 'Show'} equalizer`"
|
||||||
class="equalizer"
|
class="equalizer"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
exports[`renders 1`] = `
|
exports[`renders 1`] = `
|
||||||
<div class="extra-controls" data-testid="other-controls" data-v-8bf5fe81="">
|
<div class="extra-controls" data-testid="other-controls" data-v-8bf5fe81="">
|
||||||
<div class="wrapper" data-v-8bf5fe81="">
|
<div class="wrapper" data-v-8bf5fe81="">
|
||||||
<!--v-if--><button class="visualizer-btn" data-testid="toggle-visualizer-btn" title="Show/hide the visualizer" type="button" data-v-8bf5fe81=""><br data-testid="icon" icon="[object Object]" data-v-8bf5fe81=""></button>
|
<!--v-if--><button class="visualizer-btn" data-testid="toggle-visualizer-btn" title="Toggle the visualizer" type="button" data-v-8bf5fe81=""><br data-testid="icon" icon="[object Object]" data-v-8bf5fe81=""></button>
|
||||||
<!--v-if--><br data-testid="Volume" data-v-8bf5fe81="">
|
<!--v-if--><br data-testid="Volume" data-v-8bf5fe81="">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
exports[`renders with a current song 1`] = `
|
exports[`renders with a current song 1`] = `
|
||||||
<div class="playback-controls" data-testid="footer-middle-pane" data-v-2e8b419d="">
|
<div class="playback-controls" data-testid="footer-middle-pane" data-v-2e8b419d="">
|
||||||
<div class="buttons" data-v-2e8b419d=""><button title="Unlike Fahrstuhl to Heaven by Led Zeppelin" data-testid="like-btn" type="button" class="like-btn" data-v-2e8b419d=""><br data-testid="btn-like-liked" icon="[object Object]"></button><!-- a placeholder to maintain the flex layout --><button type="button" title="Play previous song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><br data-testid="PlayButton" data-v-2e8b419d=""><button type="button" title="Play next song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><button class="repeat-mode-btn" title="Change repeat mode (current mode: No Repeat)" data-testid="repeat-mode-switch" type="button" data-v-cab48a7c="" data-v-2e8b419d="">
|
<div class="buttons" data-v-2e8b419d=""><button title="Unlike Fahrstuhl to Heaven by Led Zeppelin" data-testid="like-btn" type="button" class="like-btn" data-v-2e8b419d=""><br data-testid="btn-like-liked" icon="[object Object]"></button><!-- a placeholder to maintain the flex layout --><button type="button" title="Play previous song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><br data-testid="PlayButton" data-v-2e8b419d=""><button type="button" title="Play next song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><button class="repeat-mode-btn" title="Change repeat mode (current: No Repeat)" data-testid="repeat-mode-switch" type="button" data-v-cab48a7c="" data-v-2e8b419d="">
|
||||||
<div class="fa-layers" data-v-cab48a7c=""><br data-testid="icon" icon="[object Object]" data-v-cab48a7c="">
|
<div class="fa-layers" data-v-cab48a7c=""><br data-testid="icon" icon="[object Object]" data-v-cab48a7c="">
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,7 +12,7 @@ exports[`renders with a current song 1`] = `
|
||||||
|
|
||||||
exports[`renders without a current song 1`] = `
|
exports[`renders without a current song 1`] = `
|
||||||
<div class="playback-controls" data-testid="footer-middle-pane" data-v-2e8b419d="">
|
<div class="playback-controls" data-testid="footer-middle-pane" data-v-2e8b419d="">
|
||||||
<div class="buttons" data-v-2e8b419d=""><button type="button" data-v-2e8b419d=""></button><!-- a placeholder to maintain the flex layout --><button type="button" title="Play previous song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><br data-testid="PlayButton" data-v-2e8b419d=""><button type="button" title="Play next song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><button class="repeat-mode-btn" title="Change repeat mode (current mode: No Repeat)" data-testid="repeat-mode-switch" type="button" data-v-cab48a7c="" data-v-2e8b419d="">
|
<div class="buttons" data-v-2e8b419d=""><button type="button" data-v-2e8b419d=""></button><!-- a placeholder to maintain the flex layout --><button type="button" title="Play previous song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><br data-testid="PlayButton" data-v-2e8b419d=""><button type="button" title="Play next song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><button class="repeat-mode-btn" title="Change repeat mode (current: No Repeat)" data-testid="repeat-mode-switch" type="button" data-v-cab48a7c="" data-v-2e8b419d="">
|
||||||
<div class="fa-layers" data-v-cab48a7c=""><br data-testid="icon" icon="[object Object]" data-v-cab48a7c="">
|
<div class="fa-layers" data-v-cab48a7c=""><br data-testid="icon" icon="[object Object]" data-v-cab48a7c="">
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<button title="About Koel" type="button" @click.prevent="openAboutKoelModal">
|
<button v-koel-tooltip.left title="About Koel" type="button" @click.prevent="openAboutKoelModal">
|
||||||
<icon :icon="faInfoCircle"/>
|
<icon :icon="faInfoCircle"/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button title="Log out" type="button" @click.prevent="logout">
|
<button v-koel-tooltip.left title="Log out" type="button" @click.prevent="logout">
|
||||||
<icon :icon="faArrowRightFromBracket"/>
|
<icon :icon="faArrowRightFromBracket"/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
:isFirstGroup="index === 0"
|
:isFirstGroup="index === 0"
|
||||||
@input="onGroupChanged"
|
@input="onGroupChanged"
|
||||||
/>
|
/>
|
||||||
<Btn class="btn-add-group" green small uppercase @click.prevent="addGroup">
|
<Btn class="btn-add-group" green small title="Add a new group" uppercase @click.prevent="addGroup">
|
||||||
<icon :icon="faPlus"/>
|
<icon :icon="faPlus"/>
|
||||||
Group
|
Group
|
||||||
</Btn>
|
</Btn>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
:isFirstGroup="index === 0"
|
:isFirstGroup="index === 0"
|
||||||
@input="onGroupChanged"
|
@input="onGroupChanged"
|
||||||
/>
|
/>
|
||||||
<Btn class="btn-add-group" green small uppercase @click.prevent="addGroup">
|
<Btn class="btn-add-group" green small title="Add a new group" uppercase @click.prevent="addGroup">
|
||||||
<icon :icon="faPlus"/>
|
<icon :icon="faPlus"/>
|
||||||
</Btn>
|
</Btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="row" data-testid="smart-playlist-rule-row">
|
<div class="row" data-testid="smart-playlist-rule-row">
|
||||||
<Btn class="remove-rule" red @click.prevent="removeRule">
|
<Btn class="remove-rule" red title="Remove this rule" @click.prevent="removeRule">
|
||||||
<icon :icon="faTrashCan"/>
|
<icon :icon="faTrashCan"/>
|
||||||
</Btn>
|
</Btn>
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
|
|
||||||
<Btn
|
<Btn
|
||||||
v-if="showDeletePlaylistButton"
|
v-if="showDeletePlaylistButton"
|
||||||
|
v-koel-tooltip
|
||||||
class="del btn-delete-playlist"
|
class="del btn-delete-playlist"
|
||||||
red
|
red
|
||||||
title="Delete this playlist"
|
title="Delete this playlist"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
id="extraTabLyrics"
|
id="extraTabLyrics"
|
||||||
|
v-koel-tooltip.left
|
||||||
:class="{ active: value === 'Lyrics' }"
|
:class="{ active: value === 'Lyrics' }"
|
||||||
title="Lyrics"
|
title="Lyrics"
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -10,6 +11,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="extraTabArtist"
|
id="extraTabArtist"
|
||||||
|
v-koel-tooltip.left
|
||||||
:class="{ active: value === 'Artist' }"
|
:class="{ active: value === 'Artist' }"
|
||||||
title="Artist information"
|
title="Artist information"
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -19,6 +21,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="extraTabAlbum"
|
id="extraTabAlbum"
|
||||||
|
v-koel-tooltip.left
|
||||||
:class="{ active: value === 'Album' }"
|
:class="{ active: value === 'Album' }"
|
||||||
title="Album information"
|
title="Album information"
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -28,6 +31,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="useYouTube"
|
v-if="useYouTube"
|
||||||
|
v-koel-tooltip.left
|
||||||
id="extraTabYouTube"
|
id="extraTabYouTube"
|
||||||
:class="{ active: value === 'YouTube' }"
|
:class="{ active: value === 'YouTube' }"
|
||||||
title="Related YouTube videos"
|
title="Related YouTube videos"
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<a class="view-profile" data-testid="view-profile-link" href="/#/profile" title="View/edit user profile">
|
<a
|
||||||
|
v-koel-tooltip.left
|
||||||
|
class="view-profile"
|
||||||
|
data-testid="view-profile-link"
|
||||||
|
href="/#/profile"
|
||||||
|
title="Profile and preferences"
|
||||||
|
>
|
||||||
<img :alt="`Avatar of ${currentUser.name}`" :src="currentUser.avatar"/>
|
<img :alt="`Avatar of ${currentUser.name}`" :src="currentUser.avatar"/>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
|
v-koel-tooltip.top
|
||||||
:class="{ active: mode !== 'NO_REPEAT' }"
|
:class="{ active: mode !== 'NO_REPEAT' }"
|
||||||
:title="`Change repeat mode (current mode: ${readableMode})`"
|
:title="`Change repeat mode (current: ${readableMode})`"
|
||||||
data-testid="repeat-mode-switch"
|
data-testid="repeat-mode-switch"
|
||||||
type="button"
|
type="button"
|
||||||
@click.prevent="changeMode"
|
@click.prevent="changeMode"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="view-modes">
|
<span class="view-modes">
|
||||||
<label
|
<label
|
||||||
|
v-koel-tooltip
|
||||||
:class="{ active: value === 'thumbnails' }"
|
:class="{ active: value === 'thumbnails' }"
|
||||||
class="thumbnails"
|
class="thumbnails"
|
||||||
data-testid="view-mode-thumbnail"
|
data-testid="view-mode-thumbnail"
|
||||||
|
@ -12,6 +13,7 @@
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
|
v-koel-tooltip
|
||||||
:class="{ active: value === 'list' }"
|
:class="{ active: value === 'list' }"
|
||||||
class="list"
|
class="list"
|
||||||
data-testid="view-mode-list"
|
data-testid="view-mode-list"
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
<template>
|
<template>
|
||||||
<span id="volume" class="volume" :class="level">
|
<span id="volume" class="volume" :class="level">
|
||||||
<icon
|
<span
|
||||||
v-if="level === 'muted'"
|
v-show="level === 'muted'"
|
||||||
:icon="faVolumeMute"
|
v-koel-tooltip.top
|
||||||
fixed-width
|
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
title="Unmute"
|
title="Unmute"
|
||||||
@click="unmute"
|
@click="unmute"
|
||||||
/>
|
>
|
||||||
<icon
|
<icon :icon="faVolumeMute" fixed-width/>
|
||||||
v-else
|
</span>
|
||||||
:icon="level === 'discreet' ? faVolumeLow : faVolumeHigh"
|
|
||||||
fixed-width
|
<span
|
||||||
|
v-show="level !== 'muted'"
|
||||||
|
v-koel-tooltip.top
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
title="Mute"
|
title="Mute"
|
||||||
@click="mute"
|
@click="mute"
|
||||||
/>
|
>
|
||||||
|
<icon :icon="level === 'discreet' ? faVolumeLow : faVolumeHigh" fixed-width/>
|
||||||
|
</span>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
id="volumeInput"
|
id="volumeInput"
|
||||||
|
@ -75,7 +78,6 @@ eventBus.on('KOEL_READY', () => setLevel(preferences.volume))
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
[type=range] {
|
[type=range] {
|
||||||
margin: 0 0 0 8px;
|
margin: 0 0 0 8px;
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
// Vitest Snapshot v1
|
// Vitest Snapshot v1
|
||||||
|
|
||||||
exports[`renders 1`] = `<a class="view-profile" data-testid="view-profile-link" href="/#/profile" title="View/edit user profile" data-v-663f2e50=""><img alt="Avatar of John Doe" src="https://example.com/avatar.jpg" data-v-663f2e50=""></a>`;
|
exports[`renders 1`] = `<a class="view-profile" data-testid="view-profile-link" href="/#/profile" title="Profile and preferences" data-v-663f2e50=""><img alt="Avatar of John Doe" src="https://example.com/avatar.jpg" data-v-663f2e50=""></a>`;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './clickaway'
|
export * from './clickaway'
|
||||||
export * from './focus'
|
export * from './focus'
|
||||||
|
export * from './tooltip'
|
||||||
|
|
110
resources/assets/js/directives/tooltip.ts
Normal file
110
resources/assets/js/directives/tooltip.ts
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import { arrow, autoUpdate, computePosition, offset, Placement } from '@floating-ui/dom'
|
||||||
|
import { Directive, DirectiveBinding } from 'vue'
|
||||||
|
|
||||||
|
type ElementWithTooltip = HTMLElement & {
|
||||||
|
$tooltip?: HTMLDivElement,
|
||||||
|
$cleanup?: Closure
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOrCreateTooltip = (el: ElementWithTooltip): HTMLElement => {
|
||||||
|
if (el.$tooltip) return el.$tooltip
|
||||||
|
|
||||||
|
el.$tooltip = document.createElement('div')
|
||||||
|
el.$tooltip.classList.add('tooltip')
|
||||||
|
|
||||||
|
const arrow = document.createElement('div')
|
||||||
|
arrow.classList.add('tooltip-arrow')
|
||||||
|
|
||||||
|
const content = document.createElement('div')
|
||||||
|
content.classList.add('tooltip-content')
|
||||||
|
|
||||||
|
el.$tooltip.appendChild(content)
|
||||||
|
el.$tooltip.appendChild(arrow)
|
||||||
|
|
||||||
|
document.body.appendChild(el.$tooltip)
|
||||||
|
|
||||||
|
return el.$tooltip
|
||||||
|
}
|
||||||
|
|
||||||
|
const init = (el: ElementWithTooltip, binding: DirectiveBinding) => {
|
||||||
|
const $tooltip = getOrCreateTooltip(el)
|
||||||
|
|
||||||
|
// make sure the actual title is removed from the element, but keep a backup for the updated() hook calls
|
||||||
|
$tooltip.querySelector<HTMLDivElement>('.tooltip-content')!.innerText = binding.value
|
||||||
|
|| el.title
|
||||||
|
|| el.getAttribute('data-title')
|
||||||
|
|| el.innerText
|
||||||
|
|
||||||
|
if (el.title && !el.getAttribute('data-title')) {
|
||||||
|
el.setAttribute('data-title', el.title)
|
||||||
|
el.removeAttribute('title')
|
||||||
|
}
|
||||||
|
|
||||||
|
const $arrow = $tooltip.querySelector<HTMLDivElement>('.tooltip-arrow')!
|
||||||
|
|
||||||
|
let placement: Placement = 'bottom'
|
||||||
|
|
||||||
|
;(['left', 'right', 'top', 'bottom'] as Placement[]).forEach(p => {
|
||||||
|
if (binding.modifiers[p]) {
|
||||||
|
placement = p
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const update = async () => {
|
||||||
|
const { x, y, middlewareData } = await computePosition(el, $tooltip, {
|
||||||
|
placement,
|
||||||
|
middleware: [
|
||||||
|
arrow({ element: $arrow }),
|
||||||
|
offset(8)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
Object.assign($tooltip.style, {
|
||||||
|
top: `${y}px`,
|
||||||
|
left: `${x}px`
|
||||||
|
})
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const { x: arrowX, y: arrowY } = middlewareData.arrow
|
||||||
|
|
||||||
|
const staticSide = {
|
||||||
|
top: 'bottom',
|
||||||
|
right: 'left',
|
||||||
|
bottom: 'top',
|
||||||
|
left: 'right'
|
||||||
|
}[placement.split('-')[0]]
|
||||||
|
|
||||||
|
Object.assign($arrow.style, {
|
||||||
|
left: arrowX != null ? `${arrowX}px` : '',
|
||||||
|
top: arrowY != null ? `${arrowY}px` : '',
|
||||||
|
right: '',
|
||||||
|
bottom: '',
|
||||||
|
// @ts-ignore
|
||||||
|
[staticSide]: '-4px'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
el.$cleanup = el.$cleanup || autoUpdate(el, $tooltip, update)
|
||||||
|
|
||||||
|
const showTooltip = async () => {
|
||||||
|
$tooltip.classList.add('show')
|
||||||
|
await update()
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideTooltip = () => $tooltip.classList.remove('show')
|
||||||
|
|
||||||
|
el.addEventListener('mouseenter', showTooltip)
|
||||||
|
el.addEventListener('focus', showTooltip)
|
||||||
|
el.addEventListener('mouseleave', hideTooltip)
|
||||||
|
el.addEventListener('blur', hideTooltip)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tooltip: Directive = {
|
||||||
|
mounted: init,
|
||||||
|
updated: init,
|
||||||
|
|
||||||
|
unmounted: (el: ElementWithTooltip, binding) => {
|
||||||
|
el.$cleanup && el.$cleanup()
|
||||||
|
el.$tooltip && document.removeChild(el.$tooltip)
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,4 +11,5 @@
|
||||||
@import "#/vendor/_nprogress.scss";
|
@import "#/vendor/_nprogress.scss";
|
||||||
|
|
||||||
@import "#/partials/_skeleton.scss";
|
@import "#/partials/_skeleton.scss";
|
||||||
|
@import '#/partials/_tooltip.scss';
|
||||||
@import "#/partials/_shared.scss";
|
@import "#/partials/_shared.scss";
|
||||||
|
|
29
resources/assets/sass/partials/_tooltip.scss
Normal file
29
resources/assets/sass/partials/_tooltip.scss
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
.tooltip {
|
||||||
|
opacity: 0;
|
||||||
|
color: rgba(255, 255, 255, .8);
|
||||||
|
width: max-content;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: #111;
|
||||||
|
padding: 5px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
pointer-events: none;
|
||||||
|
filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, .3));
|
||||||
|
z-index: 9999;
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity .5s ease-in-out;
|
||||||
|
transition-delay: .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-arrow {
|
||||||
|
position: absolute;
|
||||||
|
background: #111;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -978,6 +978,18 @@
|
||||||
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-6.3.1.tgz#1ae963dd40405450a2945408cba553e1afa3e0fb"
|
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-6.3.1.tgz#1ae963dd40405450a2945408cba553e1afa3e0fb"
|
||||||
integrity sha512-8YXBE2ZcU/pImVOHX7MWrSR/X5up7t6rPWZlk34RwZEcdr3ua6X+32pSd6XuOQRN+vbuvYNfA6iey8NbrjuMFQ==
|
integrity sha512-8YXBE2ZcU/pImVOHX7MWrSR/X5up7t6rPWZlk34RwZEcdr3ua6X+32pSd6XuOQRN+vbuvYNfA6iey8NbrjuMFQ==
|
||||||
|
|
||||||
|
"@floating-ui/core@^1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.0.1.tgz#00e64d74e911602c8533957af0cce5af6b2e93c8"
|
||||||
|
integrity sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA==
|
||||||
|
|
||||||
|
"@floating-ui/dom@^1.0.3":
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.0.3.tgz#b439c8a66436c2cae8d97e889f0b76cce757a6ec"
|
||||||
|
integrity sha512-6H1kwjkOZKabApNtXRiYHvMmYJToJ1DV7rQ3xc/WJpOABhQIOJJOdz2AOejj8X+gcybaFmBpisVTZxBZAM3V0w==
|
||||||
|
dependencies:
|
||||||
|
"@floating-ui/core" "^1.0.1"
|
||||||
|
|
||||||
"@fortawesome/fontawesome-common-types@6.2.0":
|
"@fortawesome/fontawesome-common-types@6.2.0":
|
||||||
version "6.2.0"
|
version "6.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz#76467a94aa888aeb22aafa43eb6ff889df3a5a7f"
|
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz#76467a94aa888aeb22aafa43eb6ff889df3a5a7f"
|
||||||
|
|
Loading…
Reference in a new issue