Add new temporary tooltip component. Improve tooltips and user avatars
This commit is contained in:
parent
4129f166e6
commit
99ef4f2992
|
@ -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 */
|
||||
|
|
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue