diff --git a/packages/ui/src/components/Tooltip.svelte b/packages/ui/src/components/Tooltip.svelte index a7af624202..6f877a9b69 100644 --- a/packages/ui/src/components/Tooltip.svelte +++ b/packages/ui/src/components/Tooltip.svelte @@ -15,11 +15,13 @@ <script lang="ts"> import type { IntlString } from '@anticrm/platform' - import type { TooltipAligment } from '..' - import { showTooltip, closeTooltip } from '..' + import type { TooltipAligment, AnySvelteComponent, AnyComponent } from '..' + import { showTooltip } from '..' - export let label: IntlString + export let label: IntlString | undefined export let direction: TooltipAligment | undefined + export let component: AnySvelteComponent | AnyComponent | undefined = undefined + export let props: any | undefined = undefined let triggerHTML: HTMLElement </script> @@ -27,11 +29,8 @@ <div class="tooltip-trigger" bind:this={triggerHTML} - on:mouseenter={() => { - showTooltip(label, triggerHTML, direction) - }} - on:mouseleave={() => { - closeTooltip() + on:mousemove={() => { + showTooltip(label, triggerHTML, direction, component, props) }} > <slot /> diff --git a/packages/ui/src/components/TooltipInstance.svelte b/packages/ui/src/components/TooltipInstance.svelte index b20c800558..3dd0e7006a 100644 --- a/packages/ui/src/components/TooltipInstance.svelte +++ b/packages/ui/src/components/TooltipInstance.svelte @@ -13,44 +13,77 @@ // limitations under the License. --> <script lang="ts"> - import { tooltipstore as tooltip } from '..' + import { tooltipstore as tooltip, closeTooltip } from '..' import type { TooltipAligment } from '..' import Label from './Label.svelte' let tooltipHTML: HTMLElement let dir: TooltipAligment + let rect: DOMRect $: { if ($tooltip.label && tooltipHTML) { if ($tooltip.element) { - const rect = $tooltip.element.getBoundingClientRect() + 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 ($tooltip.component) { + + if (rect.bottom + tooltipHTML.clientHeight + 28 < doc.height) { + tooltipHTML.style.top = `calc(${rect.bottom}px + .75rem)` + dir = 'bottom' + } else if (rect.top > doc.height - rect.bottom) { + tooltipHTML.style.bottom = `calc(${doc.height - rect.y}px + .75rem)` + if (tooltipHTML.clientHeight > rect.top - 28) { + tooltipHTML.style.top = '1rem' + tooltipHTML.style.height = rect.top - 28 + 'px' + } + dir = 'top' + } else { + tooltipHTML.style.top = `calc(${rect.bottom}px + .75rem)` + if (tooltipHTML.clientHeight > doc.height - rect.bottom - 28) { + tooltipHTML.style.bottom = '1rem' + tooltipHTML.style.height = doc.height - rect.bottom - 28 + 'px' + } + dir = 'bottom' + } + if (rect.left + tooltipHTML.clientWidth + 16 > doc.width) { + tooltipHTML.style.left = '' + tooltipHTML.style.right = doc.width - rect.right + 'px' + } else { + tooltipHTML.style.left = rect.left + 'px' + tooltipHTML.style.right = '' + } + + } else { + + 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') - 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%' @@ -62,15 +95,60 @@ tooltipHTML.style.visibility = 'visible' } else if (tooltipHTML) tooltipHTML.style.visibility = 'hidden' } + + const hideTooltip = (): void => { + tooltipHTML.style.visibility = 'hidden' + closeTooltip() + } + + const whileShow = (ev: MouseEvent): void => { + if ($tooltip.element) { + const rectP = tooltipHTML.getBoundingClientRect() + const topT = (dir === 'top') ? rect.top - 16 : rect.top + const bottomT = (dir === 'bottom') ? rect.bottom + 16 : rect.bottom + const leftT = (dir === 'left') ? rect.left - 16 : rect.left + const rightT = (dir === 'right') ? rect.right + 16 : rect.right + if (!((ev.x >= leftT && ev.x <= rightT && ev.y >= topT && ev.y <= bottomT) || + (ev.x >= rectP.left && ev.x <= rectP.right && ev.y >= rectP.top && ev.y <= rectP.bottom)) + ) hideTooltip() + } + } </script> -{#if $tooltip.label} +<svelte:window on:mousemove={(ev) => { whileShow(ev) }} /> +{#if $tooltip.component} + <div class="popup" bind:this={tooltipHTML}> + {#if $tooltip.label}<div class="header"><Label label={$tooltip.label} /></div>{/if} + <svelte:component this={$tooltip.component} {...$tooltip.props} /> + </div> +{:else if $tooltip.label} <div class="tooltip {dir}" bind:this={tooltipHTML}> <Label label={$tooltip.label} /> </div> {/if} <style lang="scss"> + .header { + margin-bottom: 1.5rem; + font-weight: 500; + font-size: 1rem; + color: var(--theme-caption-color); + } + + .popup { + position: fixed; + display: flex; + flex-direction: column; + padding: 1rem; + color: var(--theme-caption-color); + background-color: var(--theme-button-bg-hovered); + border: 1px solid var(--theme-button-border-enabled); + border-radius: .75rem; + user-select: none; + filter: drop-shadow(0 1.5rem 4rem rgba(0, 0, 0, .35)); + z-index: 1000; + } + .tooltip { position: fixed; padding: .5rem; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index a5807a4226..edd65f1232 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -120,15 +120,17 @@ export function closePopup (): void { export const tooltipstore = writable<LabelAndProps>({ label: undefined, element: undefined, - direction: undefined + direction: undefined, + component: undefined, + props: undefined }) -export function showTooltip (label: IntlString, element: HTMLElement, direction?: TooltipAligment): void { - tooltipstore.set({ label: label, element: element, direction: direction }) +export function showTooltip (label: IntlString | undefined, element: HTMLElement, direction?: TooltipAligment, component?: AnySvelteComponent | AnyComponent, props?: any): void { + tooltipstore.set({ label: label, element: element, direction: direction, component: component, props: props }) } export function closeTooltip (): void { - tooltipstore.set({ label: undefined, element: undefined, direction: undefined }) + tooltipstore.set({ label: undefined, element: undefined, direction: undefined, component: undefined, props: undefined }) } export const ticker = readable(Date.now(), set => { diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index ebdf3dae33..65c7bc9921 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -62,4 +62,6 @@ export interface LabelAndProps { label: IntlString | undefined element: HTMLElement | undefined direction?: TooltipAligment + component?: AnySvelteComponent | AnyComponent + props?: any } diff --git a/plugins/chunter-resources/src/components/AttachmentPopup.svelte b/plugins/chunter-resources/src/components/AttachmentPopup.svelte new file mode 100644 index 0000000000..c765d21ea5 --- /dev/null +++ b/plugins/chunter-resources/src/components/AttachmentPopup.svelte @@ -0,0 +1,87 @@ +<!-- +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// Copyright © 2021 Hardcore Engineering Inc. +// +// 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 type { Bag } from '@anticrm/core' + import type { Attachment } from '@anticrm/chunter' + import { showPopup, closeTooltip } from '@anticrm/ui' + import { PDFViewer } from '@anticrm/presentation' + + export let files: Bag<Attachment> + + const maxLenght: number = 32 + const trimFilename = (fname: string): string => (fname.length > maxLenght) + ? fname.substr(0, (maxLenght - 1) / 2) + '...' + fname.substr(-(maxLenght - 1) / 2) + : fname +</script> + +<table class="table-body"> + <tbody> + {#each Object.values(files) as file} + <tr class="tr-body"> + <td class="item flex-row-center"> + <div class="flex-center file-icon">pdf</div> + <div class="flex-col flex-grow" style="cursor: pointer" on:click={() => { + closeTooltip() + showPopup(PDFViewer, { file: file.file }, 'right') + }}> + <div class="overflow-label caption-color">{trimFilename(file.name)}</div> + <div class="overflow-label file-desc">{file.type}</div> + </div> + </td> + <td>10 / 8</td> + </tr> + {/each} + </tbody> +</table> + +<style lang="scss"> + th, td { + padding: .75rem 0; + text-align: left; + } + th { + font-weight: 500; + font-size: .75rem; + color: var(--theme-content-dark-color); + } + td { + color: var(--theme-caption-color); + } + .tr-body { + border-top: 1px solid var(--theme-button-border-hovered); + &:first-child { border-top: none; } + } + + .item { padding: .75rem 1rem .75rem 0; } + .file-icon { + margin-right: 1.25rem; + width: 2rem; + height: 2rem; + font-weight: 500; + font-size: 0.625rem; + line-height: 150%; + text-transform: uppercase; + color: #fff; + background-color: var(--primary-button-enabled); + border: 1px solid rgba(0, 0, 0, .1); + border-radius: .5rem; + } + .file-desc { + font-size: 0.75rem; + color: var(--theme-content-dark-color); + } +</style> diff --git a/plugins/chunter-resources/src/components/AttachmentPresenter.svelte b/plugins/chunter-resources/src/components/AttachmentPresenter.svelte index ca0e9b65b8..357774d77a 100644 --- a/plugins/chunter-resources/src/components/AttachmentPresenter.svelte +++ b/plugins/chunter-resources/src/components/AttachmentPresenter.svelte @@ -17,9 +17,9 @@ <script lang="ts"> import type { Bag } from '@anticrm/core' import type { Attachment } from '@anticrm/chunter' - import { IconFile, Link, showPopup } from '@anticrm/ui' + import { IconFile, Link, Tooltip, showPopup } from '@anticrm/ui' import { PDFViewer } from '@anticrm/presentation' - import FileGroup from './FileGroup.svelte' + import AttachmentPopup from './AttachmentPopup.svelte' export let value: { attachments: Bag<Attachment> } @@ -28,5 +28,7 @@ {#if Object.keys(value.attachments).length === 1} <Link label={Object.values(value.attachments)[0].name} href={'#'} icon={IconFile} on:click={ () => { showPopup(PDFViewer, { file: Object.values(value.attachments)[0].file }, 'right') } }/> {:else if Object.keys(value.attachments).length > 1} - <FileGroup files={value.attachments} /> + <Tooltip label={'Attachments (' + Object.values(value.attachments).length + ')'} component={AttachmentPopup} props={{ files: value.attachments }}> + <Link label={Object.values(value.attachments).length + ' files'} href={'#'} icon={IconFile} /> + </Tooltip> {/if} diff --git a/plugins/recruit-resources/src/components/ApplicationsPopup.svelte b/plugins/recruit-resources/src/components/ApplicationsPopup.svelte index 4764fcfb72..c8af1a4c06 100644 --- a/plugins/recruit-resources/src/components/ApplicationsPopup.svelte +++ b/plugins/recruit-resources/src/components/ApplicationsPopup.svelte @@ -26,11 +26,7 @@ { label: 'Principal analyst', description: 'Google' }] </script> -<div class="flex-col popup"> - <div class="header"> - <Label label={'Applications'} /> ({value.applications}) - </div> - +<div class="flex-col"> {#each apps as app} <div class="flex-row-center app"> <div class="app-icon"><CircleButton icon={Vacancy} size={'large'} /></div> @@ -43,47 +39,30 @@ </div> <style lang="scss"> - .popup { - display: flex; - flex-direction: column; - padding: 1.25rem 1.5rem; - background-color: var(--theme-button-bg-focused); - border: 1px solid var(--theme-button-border-enabled); - border-radius: .75rem; - box-shadow: 0 .75rem 1.25rem rgba(0, 0, 0, .2); - - .header { - margin-bottom: 1.5rem; - font-weight: 500; - font-size: 1rem; - color: var(--theme-caption-color); + .app { + position: relative; + .app-icon { + margin-right: 1.25rem; + width: 2rem; + height: 2rem; } - - .app { - position: relative; - .app-icon { - margin-right: 1.25rem; - width: 2rem; - height: 2rem; - } - .label { color: var(--theme-caption-color); } - .desc { - font-size: .75rem; - color: var(--theme-content-dark-color); - } + .label { color: var(--theme-caption-color); } + .desc { + font-size: .75rem; + color: var(--theme-content-dark-color); } + } - .app + .app { - margin-top: 1.5rem; - &::before { - content: ''; - position: absolute; - top: -.75rem; - left: 0; - width: 100%; - height: 1px; - background-color: var(--theme-button-border-hovered); - } + .app + .app { + margin-top: 1.5rem; + &::before { + content: ''; + position: absolute; + top: -.75rem; + left: 0; + width: 100%; + height: 1px; + background-color: var(--theme-button-border-hovered); } } </style> diff --git a/plugins/recruit-resources/src/components/ApplicationsPresenter.svelte b/plugins/recruit-resources/src/components/ApplicationsPresenter.svelte index 9b59d3a6d8..5f4802727a 100644 --- a/plugins/recruit-resources/src/components/ApplicationsPresenter.svelte +++ b/plugins/recruit-resources/src/components/ApplicationsPresenter.svelte @@ -17,7 +17,7 @@ <script lang="ts"> import type { Candidate } from '@anticrm/recruit' - import { CircleButton, IconFile, Label, showPopup, closePopup } from '@anticrm/ui' + import { CircleButton, IconFile, Label, Tooltip } from '@anticrm/ui' import Vacancy from './icons/Vacancy.svelte' import ApplicationsPopup from './ApplicationsPopup.svelte' @@ -29,43 +29,20 @@ </script> {#if value.applications && value.applications > 0} - <div class="apps-container" - bind:this={trigger} - on:mouseenter={() => { showPopup(ApplicationsPopup, {value}, trigger) }} - on:mouseleave={() => { closePopup() }} - > - <div class="icon"><IconFile size={'small'} /></div> - {value.applications} - </div> + <Tooltip label={'Applications'} component={ApplicationsPopup} props={{ value: value }}> + <div class="flex-row-center"> + <div class="icon"><IconFile size={'small'} /></div> + {value.applications} + </div> + </Tooltip> {/if} <style lang="scss"> - .apps-container { - position: relative; - display: flex; - align-items: center; - color: var(--theme-content-color); - cursor: pointer; - - .icon { - margin-right: .25rem; - transform-origin: center center; - transform: scale(.75); - opacity: .6; - } - &:hover { - color: var(--theme-caption-color); - .icon { opacity: 1; } - // &::after { content: ''; } - } - // &::after { - // position: absolute; - // top: 0; - // left: 0; - // right: 0; - // bottom: -2rem; - // background-color: rgba(255, 255, 0, .2); - // } + .icon { + margin-right: .25rem; + transform-origin: center center; + transform: scale(.75); + opacity: .6; } </style> diff --git a/plugins/recruit-resources/src/components/Attachments.svelte b/plugins/recruit-resources/src/components/Attachments.svelte index 41bc4585ea..52b3a2d362 100644 --- a/plugins/recruit-resources/src/components/Attachments.svelte +++ b/plugins/recruit-resources/src/components/Attachments.svelte @@ -147,9 +147,6 @@ margin-right: 1.25rem; width: 2rem; height: 2rem; - border-radius: .5rem; - } - .file-icon { font-weight: 500; font-size: 0.625rem; line-height: 150%; @@ -157,6 +154,7 @@ color: #fff; background-color: var(--primary-button-enabled); border: 1px solid rgba(0, 0, 0, .1); + border-radius: .5rem; } .file-desc { font-size: 0.75rem;