Add new temporary tooltip component. Improve tooltips and user avatars

This commit is contained in:
Andrew Kingston 2023-07-07 14:46:41 +01:00
parent 4129f166e6
commit 99ef4f2992
10 changed files with 186 additions and 137 deletions

View File

@ -5,7 +5,6 @@
Bottom: "bottom",
Left: "left",
}
export const TooltipType = {
Default: "default",
Info: "info",
@ -23,14 +22,27 @@
export let type = TooltipType.Default
export let text = ""
export let fixed = false
export let color = null
let wrapper
let hovered = false
let left = 0
let top = 0
let visible = false
let timeout
$: visible = hovered || fixed
$: {
if (hovered || fixed) {
timeout = setTimeout(show, 200)
} else {
clearTimeout(timeout)
hide()
}
}
$: tooltipStyle = color ? `background:${color};` : null
$: tipStyle = color ? `border-top-color:${color};` : null
// Computes the position of the tooltip then shows it
const show = () => {
const node = wrapper?.children?.[0]
if (!node) {
@ -38,6 +50,7 @@
}
const bounds = node.getBoundingClientRect()
// Determine where to render tooltip based on position prop
if (position === TooltipPosition.Top) {
left = bounds.left + bounds.width / 2
top = bounds.top
@ -54,36 +67,36 @@
return
}
hovered = true
visible = true
}
// Hides the tooltip
const hide = () => {
hovered = false
visible = false
}
</script>
{#if text}
<div
bind:this={wrapper}
class="abs-tooltip"
on:mouseover={show}
on:mouseleave={hide}
>
<slot />
</div>
{#if visible}
<Portal target=".spectrum">
<span
class="spectrum-Tooltip spectrum-Tooltip--{type} spectrum-Tooltip--{position} is-open"
style="left:{left}px;top:{top}px;"
transition:fade|local={{ duration: 130 }}
>
<span class="spectrum-Tooltip-label">{text}</span>
<span class="spectrum-Tooltip-tip" />
</span>
</Portal>
{/if}
{:else}
<div
bind:this={wrapper}
class="abs-tooltip"
on:focus={null}
on:mouseover={() => (hovered = true)}
on:mouseleave={() => (hovered = false)}
>
<slot />
</div>
{#if visible && text}
<Portal target=".spectrum">
<span
class="spectrum-Tooltip spectrum-Tooltip--{type} spectrum-Tooltip--{position} is-open"
style={`left:${left}px;top:${top}px;${tooltipStyle}`}
transition:fade|local={{ duration: 130 }}
>
<span class="spectrum-Tooltip-label">{text}</span>
<span class="spectrum-Tooltip-tip" style={tipStyle} />
</span>
</Portal>
{/if}
<style>
@ -101,6 +114,8 @@
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-size: 12px;
font-weight: 600;
}
/* Colour overrides for default type */

View File

@ -0,0 +1,39 @@
<script>
import AbsTooltip from "./AbsTooltip.svelte"
import { onDestroy } from "svelte"
export let text = null
export let condition = true
export let duration = 3000
export let position
export let type
let visible = false
let timeout
$: {
if (condition) {
showTooltip()
} else {
hideTooltip()
}
}
const showTooltip = () => {
visible = true
timeout = setTimeout(() => {
visible = false
}, duration)
}
const hideTooltip = () => {
visible = false
clearTimeout(timeout)
}
onDestroy(hideTooltip)
</script>
<AbsTooltip {position} {type} text={visible ? text : null} fixed={visible}>
<slot />
</AbsTooltip>

View File

@ -36,6 +36,7 @@ export { default as Layout } from "./Layout/Layout.svelte"
export { default as Page } from "./Layout/Page.svelte"
export { default as Link } from "./Link/Link.svelte"
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
export { default as TempTooltip } from "./Tooltip/TempTooltip.svelte"
export {
default as AbsTooltip,
TooltipPosition,

View File

@ -2,6 +2,7 @@
import { Icon } from "@budibase/bbui"
import { createEventDispatcher, getContext } from "svelte"
import { helpers } from "@budibase/shared-core"
import { UserAvatar } from "@budibase/frontend-core"
export let icon
export let withArrow = false
@ -98,21 +99,29 @@
<Icon color={iconColor} size="S" name={icon} />
</div>
{/if}
<div class="text" title={showTooltip ? text : null}>{text}</div>
<div class="text" title={showTooltip ? text : null}>
{text}
{#if selectedBy}
<UserAvatar user={selectedBy} size="XS" />
{/if}
</div>
{#if withActions}
<div class="actions">
<slot />
</div>
{/if}
{#if $$slots.right}
<div class="right">
<slot name="right" />
</div>
{/if}
</div>
{#if selectedBy}
<div class="selected-by-label">{helpers.getUserLabel(selectedBy)}</div>
{/if}
<!--{#if selectedBy}-->
<!-- <div class="selected-by-label">{helpers.getUserLabel(selectedBy)}</div>-->
<!--{/if}-->
</div>
<style>
@ -159,36 +168,36 @@
padding-left: var(--spacing-l);
}
/* Selected user styles */
.nav-item.selectedBy:after {
content: "";
position: absolute;
width: calc(100% - 4px);
height: 28px;
border: 2px solid var(--selected-by-color);
left: 0;
top: 0;
border-radius: 2px;
pointer-events: none;
}
.selected-by-label {
position: absolute;
top: 0;
right: 0;
background: var(--selected-by-color);
padding: 2px 4px;
font-size: 12px;
color: white;
transform: translateY(calc(1px - 100%));
border-top-right-radius: 2px;
border-top-left-radius: 2px;
pointer-events: none;
opacity: 0;
transition: opacity 130ms ease-out;
}
.nav-item.selectedBy:hover .selected-by-label {
opacity: 1;
}
/*!* Selected user styles *!*/
/*.nav-item.selectedBy:after {*/
/* content: "";*/
/* position: absolute;*/
/* width: calc(100% - 4px);*/
/* height: 28px;*/
/* border: 2px solid var(--selected-by-color);*/
/* left: 0;*/
/* top: 0;*/
/* border-radius: 2px;*/
/* pointer-events: none;*/
/*}*/
/*.selected-by-label {*/
/* position: absolute;*/
/* top: 0;*/
/* right: 0;*/
/* background: var(--selected-by-color);*/
/* padding: 2px 4px;*/
/* font-size: 12px;*/
/* color: white;*/
/* transform: translateY(calc(1px - 100%));*/
/* border-top-right-radius: 2px;*/
/* border-top-left-radius: 2px;*/
/* pointer-events: none;*/
/* opacity: 0;*/
/* transition: opacity 130ms ease-out;*/
/*}*/
/*.nav-item.selectedBy:hover .selected-by-label {*/
/* opacity: 1;*/
/*}*/
/* Needed to fully display the actions icon */
.nav-item.scrollable .nav-item-content {
@ -245,6 +254,9 @@
color: var(--spectrum-global-color-gray-900);
order: 2;
width: 0;
display: flex;
align-items: center;
gap: 8px;
}
.scrollable .text {
flex: 0 0 auto;

View File

@ -1,5 +1,6 @@
<script>
import { UserAvatar } from "@budibase/frontend-core"
import { TooltipPosition } from "@budibase/bbui"
export let users = []
@ -15,14 +16,21 @@
</script>
<div class="avatars">
{#each uniqueUsers as user}
<UserAvatar {user} tooltipDirection="bottom" />
{#each uniqueUsers as user, idx}
<span style="z-index:{100 - idx};">
<UserAvatar {user} tooltipPosition={TooltipPosition.Bottom} />
</span>
{/each}
</div>
<style>
.avatars {
display: flex;
gap: 4px;
}
.avatars :global(> *:not(:first-child)) {
margin-left: -12px;
}
.avatars :global(.spectrum-Avatar) {
border: 2px solid var(--background);
}
</style>

View File

@ -46,7 +46,7 @@
/>
<div class="delete-action">
<AbsTooltip
position={TooltipPosition.Right}
position={TooltipPosition.Bottom}
text={$isOnlyUser
? null
: "Unavailable - another user is editing this app"}

View File

@ -1,60 +1,23 @@
<script>
import { Avatar, Tooltip } from "@budibase/bbui"
import { Avatar, AbsTooltip, TooltipPosition } from "@budibase/bbui"
import { helpers } from "@budibase/shared-core"
export let user
export let size
export let tooltipDirection = "top"
export let tooltipPosition = TooltipPosition.Top
export let showTooltip = true
$: tooltipStyle = getTooltipStyle(tooltipDirection)
const getTooltipStyle = direction => {
if (!direction) {
return ""
}
if (direction === "top") {
return "transform: translateX(-50%) translateY(-100%);"
} else if (direction === "bottom") {
return "transform: translateX(-50%) translateY(100%);"
}
}
</script>
{#if user}
<div class="user-avatar">
<AbsTooltip
text={showTooltip ? helpers.getUserLabel(user) : null}
position={tooltipPosition}
color={helpers.getUserColor(user)}
>
<Avatar
{size}
initials={helpers.getUserInitials(user)}
color={helpers.getUserColor(user)}
/>
{#if showTooltip}
<div class="tooltip" style={tooltipStyle}>
<Tooltip
direction={tooltipDirection}
textWrapping
text={helpers.getUserLabel(user)}
size="S"
/>
</div>
{/if}
</div>
</AbsTooltip>
{/if}
<style>
.user-avatar {
position: relative;
}
.tooltip {
position: absolute;
top: 0;
left: 50%;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 130ms ease-out;
}
.user-avatar:hover .tooltip {
opacity: 1;
}
</style>

View File

@ -4,7 +4,7 @@
import HeaderCell from "../cells/HeaderCell.svelte"
import {
Icon,
AbsTooltip,
TempTooltip,
TooltipType,
TooltipPosition,
} from "@budibase/bbui"
@ -16,10 +16,11 @@
hiddenColumnsWidth,
width,
config,
hasNonAutoColumn,
} = getContext("grid")
$: columnsWidth = $renderedColumns.reduce(
(total, col) => (total += col.width),
(total, col) => total + col.width,
0
)
$: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left
@ -35,19 +36,23 @@
</div>
</GridScrollWrapper>
{#if $config.allowSchemaChanges}
<AbsTooltip
text="Click here to create your first column"
position={TooltipPosition.Bottom}
type={TooltipType.Info}
>
<div
class="add"
style="left:{left}px"
on:click={() => dispatch("add-column")}
{#key left}
<TempTooltip
text="Click here to create your first column"
position={TooltipPosition.Top}
type={TooltipType.Info}
duration={3000}
condition={!$hasNonAutoColumn}
>
<Icon name="Add" />
</div>
</AbsTooltip>
<div
class="add"
style="left:{left}px"
on:click={() => dispatch("add-column")}
>
<Icon name="Add" />
</div>
</TempTooltip>
{/key}
{/if}
</div>

View File

@ -83,6 +83,21 @@ export const deriveStores = context => {
await saveChanges()
}
// Derive if we have any normal columns
const hasNonAutoColumn = derived(
[columns, stickyColumn],
([$columns, $stickyColumn]) => {
let allCols = $columns || []
if ($stickyColumn) {
allCols = [...allCols, $stickyColumn]
}
const normalCols = allCols.filter(column => {
return !column.schema?.autocolumn
})
return normalCols.length > 0
}
)
// Persists column changes by saving metadata against table schema
const saveChanges = async () => {
const $columns = get(columns)
@ -128,6 +143,7 @@ export const deriveStores = context => {
}
return {
hasNonAutoColumn,
columns: {
...columns,
actions: {

View File

@ -70,8 +70,7 @@ export const deriveStores = context => {
rowHeight,
stickyColumn,
width,
columns,
stickyColumns,
hasNonAutoColumn,
config,
} = context
@ -117,18 +116,9 @@ export const deriveStores = context => {
// Derive if we're able to add rows
const canAddRows = derived(
[config, columns, stickyColumn],
([$config, $columns, $stickyColumn]) => {
// Check if we have a normal column
let allCols = $columns || []
if ($stickyColumn) {
allCols = [...allCols, $stickyColumn]
}
const normalCols = allCols.filter(column => {
return column.visible && !column.schema?.autocolumn
})
// Check if we're allowed to add rows
return $config.allowAddRows && normalCols.length > 0
[config, hasNonAutoColumn],
([$config, $hasNonAutoColumn]) => {
return $config.allowAddRows && $hasNonAutoColumn
}
)