New Tooltip (#167)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2021-09-09 10:34:51 +03:00 committed by GitHub
parent 9874c47cf2
commit 1f7f1b05cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 203 additions and 118 deletions

View File

@ -15,20 +15,20 @@
<script lang="ts">
import type { IntlString, Asset } from '@anticrm/platform'
import type { AnySvelteComponent } from '../types'
import type { AnySvelteComponent, TooltipAligment } from '../types'
import Icon from './Icon.svelte'
import Tooltip from './Tooltip.svelte'
export let label: IntlString
export let direction: string = 'top'
export let direction: TooltipAligment | undefined
export let icon: Asset | AnySvelteComponent
export let size: 'small' | 'medium' | 'large'
export let action: () => Promise<void>
export let invisible: boolean = false
</script>
<Tooltip label={label} direction={direction}>
<Tooltip {label} {direction}>
<button class="button {size}" on:click|stopPropagation={action}>
<div class="icon {size}" class:invisible={invisible}>
{#if typeof (icon) === 'string'}

View File

@ -48,7 +48,7 @@
<svelte:component this={component} {...item.props}/>
{/if}
</div>
<div class="icon"><ActionIcon label={'Remove'} direction={'top'} icon={Close} size={'small'} action={async () => { item.selected = false }}/></div>
<div class="icon"><ActionIcon label={'Remove'} icon={Close} size={'small'} action={async () => { item.selected = false }}/></div>
</button>
{#if byTitle }
<PopupItem bind:title={item.title} selectable bind:selected={item.selected}/>

View File

@ -15,108 +15,24 @@
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import Label from './Label.svelte'
import type { TooltipAligment } from '..'
import { showTooltip, closeTooltip } from '..'
export let label: IntlString
export let direction: 'top' | 'bottom' | 'left' | 'right' = 'top'
export let direction: TooltipAligment | undefined
let triggerHTML: HTMLElement
</script>
<div class="flex-center container">
<div class="trigger"><slot/></div>
<div class="tooltip {direction}">
<Label label={label}/>
</div>
<div
class="tooltip-trigger"
bind:this={triggerHTML}
on:mouseenter={(ev) => {
showTooltip(label, triggerHTML, direction)
}}
on:mouseleave={() => {
closeTooltip()
}}
>
<slot />
</div>
<style lang="scss">
.container {
position: relative;
.trigger:hover + .tooltip {
opacity: 1;
&.top {
transform: translateY(-.625rem);
}
&.bottom {
transform: translateY(.625rem);
}
&.right {
transform: translateX(.625rem);
}
&.left {
transform: translateX(-.625rem);
}
}
.tooltip {
box-sizing: border-box;
position: absolute;
padding: .5rem;
color: var(--theme-caption-color);
background-color: var(--theme-tooltip-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: .5rem;
box-shadow: 0px .5rem 1.25rem rgba(0, 0, 0, .25);
opacity: 0;
transition: transform .3s ease, opacity .2s ease-in-out;
pointer-events: none;
user-select: none;
text-align: center;
transition-delay: .2s;
z-index: 10;
&::after {
content: "";
position: absolute;
width: .875rem;
height: .875rem;
background-color: var(--theme-tooltip-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 0 0 .25rem;
mask-image: linear-gradient(-45deg, rgba(0, 0, 0, 1) .655rem, rgba(0, 0, 0, 0) .656rem);
}
&.top::after, &.bottom::after {
left: 50%;
margin-left: -.5rem;
}
&.top {
bottom: 100%;
box-shadow: 0px -.5rem 1.25rem rgba(0, 0, 0, .25);
&::after {
bottom: -.5rem;
transform: rotate(45deg);
}
}
&.bottom {
top: 100%;
box-shadow: 0px -.5rem 1.25rem rgba(0, 0, 0, .25);
&::after {
top: -.5rem;
transform: rotate(-135deg);
}
}
&.right::after, &.left::after {
top: 50%;
margin-top: -.5rem;
}
&.right {
left: 100%;
box-shadow: -.5rem 0px 1.25rem rgba(0, 0, 0, .25);
&::after {
left: -.4rem;
transform: rotate(135deg);
}
}
&.left {
right: 100%;
box-shadow: .5rem 0px 1.25rem rgba(0, 0, 0, .25);
&::after {
right: -.4rem;
transform: rotate(-45deg);
}
}
}
}
</style>

View File

@ -0,0 +1,146 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { tooltipstore as tooltip } from '..'
import type { TooltipAligment } from '..'
import Label from './Label.svelte'
let tooltipHTML: HTMLElement
let dir: TooltipAligment
$: {
if ($tooltip.label && tooltipHTML) {
if ($tooltip.element) {
const rect = $tooltip.element.getBoundingClientRect()
const doc = document.body.getBoundingClientRect()
if (!$tooltip.direction) {
if (rect.right < doc.width / 5) dir = 'right'
else if (rect.left > doc.width - doc.width / 5) dir = 'left'
else if (rect.top < tooltipHTML.clientHeight) dir = 'bottom'
else dir = 'top'
} else dir = $tooltip.direction
if (dir === 'right') {
tooltipHTML.style.top = rect.y + rect.height / 2 + 'px'
tooltipHTML.style.left = `calc(${rect.right}px + .75rem)`
tooltipHTML.style.transform = 'translateY(-50%)'
} else if (dir === 'left') {
tooltipHTML.style.top = rect.y + rect.height / 2 + 'px'
tooltipHTML.style.right = `calc(${doc.width - rect.x}px + .75rem)`
tooltipHTML.style.transform = 'translateY(-50%)'
} else if (dir === 'bottom') {
tooltipHTML.style.top = `calc(${rect.bottom}px + .5rem)`
tooltipHTML.style.left = rect.x + rect.width / 2 + 'px'
tooltipHTML.style.transform = 'translateX(-50%)'
} else if (dir === 'top') {
tooltipHTML.style.bottom = `calc(${doc.height - rect.y}px + .75rem)`
tooltipHTML.style.left = rect.x + rect.width / 2 + 'px'
tooltipHTML.style.transform = 'translateX(-50%)'
}
tooltipHTML.classList.remove('no-arrow')
} else {
tooltipHTML.style.top = '50%'
tooltipHTML.style.left = '50%'
tooltipHTML.style.width = 'min-content'
tooltipHTML.style.height = 'min-content'
tooltipHTML.style.transform = 'translate(-50%, -50%)'
tooltipHTML.classList.add('no-arrow')
}
tooltipHTML.style.visibility = 'visible'
} else if (tooltipHTML) tooltipHTML.style.visibility = 'hidden'
}
</script>
{#if $tooltip.label}
<div class="tooltip {dir}" bind:this={tooltipHTML}>
<Label label={$tooltip.label} />
</div>
{/if}
<style lang="scss">
.tooltip {
position: fixed;
padding: .5rem;
color: var(--theme-caption-color);
background-color: var(--theme-tooltip-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: .5rem;
user-select: none;
text-align: center;
z-index: 10;
&::after {
content: '';
position: absolute;
width: .875rem;
height: .875rem;
background-color: var(--theme-tooltip-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 0 0 3px;
clip-path: polygon(100% 25%, 100% 100%, 25% 100%);
}
&.top::after,
&.bottom::after {
left: 50%;
margin-left: -.5rem;
}
&.top {
bottom: 100%;
box-shadow: 0px 8px 20px rgba(0, 0, 0, .35);
&::after {
bottom: -.3125rem;
transform: rotate(45deg);
}
}
&.bottom {
top: 100%;
box-shadow: 0px -8px 20px rgba(0, 0, 0, .35);
&::after {
top: -.3125rem;
transform: rotate(-135deg);
}
}
&.right::after,
&.left::after {
top: 50%;
margin-top: -.5rem;
}
&.right {
left: 100%;
box-shadow: -8px 0px 20px rgba(0, 0, 0, .35);
&::after {
left: -.3125rem;
transform: rotate(135deg);
}
}
&.left {
right: 100%;
box-shadow: 8px 0px 20px rgba(0, 0, 0, .35);
&::after {
right: -.3125rem;
transform: rotate(-45deg);
}
}
}
.no-arrow {
box-shadow: 0px 0px 20px rgba(0, 0, 0, .75);
&::after {
content: none;
}
}
</style>

View File

@ -14,10 +14,12 @@
//
import { SvelteComponent } from 'svelte'
import type { AnySvelteComponent, AnyComponent, PopupAlignment, LabelAndProps, TooltipAligment } from './types'
import type { IntlString } from '@anticrm/platform'
import Root from './components/internal/Root.svelte'
export type { AnyComponent, AnySvelteComponent, Action } from './types'
export type { AnyComponent, AnySvelteComponent, Action, LabelAndProps, TooltipAligment } from './types'
// export { applicationShortcutKey } from './utils'
export { getCurrentLocation, navigate, location } from './location'
@ -32,6 +34,7 @@ export { default as Toggle } from './components/Toggle.svelte'
export { default as Dialog } from './components/Dialog.svelte'
export { default as ToggleWithLabel } from './components/ToggleWithLabel.svelte'
export { default as Tooltip } from './components/Tooltip.svelte'
export { default as TooltipInstance } from './components/TooltipInstance.svelte'
export { default as CheckBox } from './components/CheckBox.svelte'
export { default as Progress } from './components/Progress.svelte'
export { default as Tabs } from './components/Tabs.svelte'
@ -66,7 +69,6 @@ export { default as IconThread } from './components/icons/Thread.svelte'
export * from './utils'
import type { AnySvelteComponent, AnyComponent, PopupAlignment } from './types'
import { writable } from 'svelte/store'
export function createApp (target: HTMLElement): SvelteComponent {
@ -100,3 +102,17 @@ export function closePopup (): void {
return popups
})
}
export const tooltipstore = writable<LabelAndProps>({
label: undefined,
element: undefined,
direction: undefined
})
export function showTooltip (label: IntlString, element: HTMLElement, direction?: TooltipAligment): void {
tooltipstore.set({ label: label, element: element, direction: direction })
}
export function closeTooltip (): void {
tooltipstore.set({ label: undefined, element: undefined, direction: undefined })
}

View File

@ -55,3 +55,11 @@ export interface Tab {
export type TabModel = Tab[]
export type PopupAlignment = HTMLElement | 'right' | 'float'
export type TooltipAligment = 'top' | 'bottom' | 'left' | 'right'
export interface LabelAndProps {
label: IntlString | undefined
element: HTMLElement | undefined
direction?: TooltipAligment
}

View File

@ -48,10 +48,10 @@
</div>
{#if !thread}
<div class="buttons">
<div class="tool"><ActionIcon icon={IconMoreH} size={'medium'} direction={'left'}/></div>
<div class="tool"><ActionIcon icon={Bookmark} size={'medium'} direction={'left'}/></div>
<div class="tool"><ActionIcon icon={Share} size={'medium'} direction={'left'}/></div>
<div class="tool"><ActionIcon icon={Emoji} size={'medium'} direction={'left'}/></div>
<div class="tool"><ActionIcon icon={IconMoreH} size={'medium'}/></div>
<div class="tool"><ActionIcon icon={Bookmark} size={'medium'}/></div>
<div class="tool"><ActionIcon icon={Share} size={'medium'}/></div>
<div class="tool"><ActionIcon icon={Emoji} size={'medium'}/></div>
</div>
{/if}
</div>

View File

@ -24,16 +24,14 @@
export let notify: boolean
</script>
<button class="app" class:selected={selected} on:click={action}>
<Tooltip label={label} direction="right">
<Tooltip {label}>
<button class="app" class:selected={selected} on:click={action}>
<div class="icon-container" class:noty={notify}>
<Icon icon={icon} size={'large'}/>
</div>
</Tooltip>
{#if notify}
<div class="marker"/>
{/if}
</button>
{#if notify}<div class="marker"/>{/if}
</button>
</Tooltip>
<style lang="scss">
.app {

View File

@ -29,7 +29,7 @@
import SpaceHeader from './SpaceHeader.svelte'
import SpaceView from './SpaceView.svelte'
import { AnyComponent, location, Popup, showPopup } from '@anticrm/ui'
import { AnyComponent, location, Popup, showPopup, TooltipInstance } from '@anticrm/ui'
import core from '@anticrm/core'
import CreateUser from './CreateUser.svelte'
@ -92,6 +92,7 @@
<!-- <div class="aside"><Chat thread/></div> -->
</div>
<Popup />
<TooltipInstance />
{:else}
No client
{/if}