@meistrari/tela-build 1.68.0 → 1.69.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,123 @@
1
+ # TelaPresenceAvatars
2
+
3
+ Shows who else is currently viewing the same page. Two variants:
4
+
5
+ - **`pill`** (default): a "N Viewing" count followed by up to `max` overlapping round avatars on a `bg-muted` pill (8px radius, 4px vertical / 10px left / 8px right padding). Hovering the count lists viewer names (up to `tooltipMax`, then a "x more" line).
6
+ - **`avatars`**: just the overlapping avatar stack — no count label, no pill surface — at a larger default size (`xs`, 24px). Meant for page headers (canvas, workflow, agent) where the count pill is too heavy. When more than `max` viewers are present, the last visible avatar gets an 80% `neutral-950` mask with a white "+N" count on top (no extra bubble); hovering it shows the same tooltip as the pill count (up to `tooltipMax` names, then the "x more" line).
7
+
8
+ Hovering an avatar shows the viewer's name and live status on separate lines: `Viewing now`, or for away viewers a relative time like "3 min ago" when `awaySinceLabel` is provided (falling back to the plain `awayLabel`). All tooltips share one `TelaTooltipGroup`, so moving between triggers skips the open delay instead of re-animating each tooltip. Viewers with `active: false` (page open, tab hidden) render dimmed instead of disappearing. Renders nothing when the viewers list is empty, so mount points need no guards.
9
+
10
+ Overlapping avatars are separated with a `mask-image` radial-gradient (the tags-select dot technique): each avatar except the last carves a transparent 2px crescent where the next avatar overlaps, so the gap shows whatever is behind the stack instead of a hardcoded ring color. Overlap scales with avatar size (16px→4px, 24px→6px, 32px→8px, 40px→10px).
11
+
12
+ Pair it with the `usePresence` composable, which joins a Yjs awareness room over websockets (channel switching, hard stop on repeated connection failures) and exposes the other viewers as `PresenceViewer[]` — deduped per user across tabs, sorted with active viewers first, then by join time, so people who tab away drop to the back of the stack and active viewers hold the visible slots. The connection stays open while the tab is hidden; the composable publishes `active: false` so peers dim and demote the avatar rather than dropping it. The consuming app provides the websocket `url`, the auth `getToken`, and the local `self` identity.
13
+
14
+ The component is i18n-free: pass pre-translated `countLabel`, `viewingLabel`, `awayLabel`, and `moreLabel` strings, and the `awaySinceLabel` formatter, from the app layer.
15
+
16
+ ## Examples
17
+
18
+ ### Basic Usage
19
+
20
+ ```vue
21
+ <TelaPresenceAvatars
22
+ :viewers="[
23
+ { name: 'Ada Lovelace', email: 'ada@example.com', image: 'https://example.com/ada.jpg' },
24
+ { name: 'Grace Hopper', email: 'grace@example.com' },
25
+ ]"
26
+ count-label="2 Viewing"
27
+ />
28
+ ```
29
+
30
+ ### Avatars-only variant
31
+
32
+ No count label, no pill chrome, larger avatars (defaults to `xs` / 24px). Used inside canvas, workflow, and agent headers. Overflow beyond `max` darkens the last visible avatar with an 80% mask and a "+N" count, with the name-list tooltip on hover.
33
+
34
+ ```vue
35
+ <TelaPresenceAvatars
36
+ variant="avatars"
37
+ :viewers="others"
38
+ label="Viewing this canvas"
39
+ />
40
+ ```
41
+
42
+ ### Away viewers
43
+
44
+ A viewer with `active: false` renders at reduced opacity, moves behind the active viewers (`usePresence` sorts active-first), and their tooltip status switches to `awaySinceLabel(viewer.awaySince)` (e.g. "3 min ago") or the `awayLabel` when no timestamp/formatter is available. `usePresence` publishes `awaySince` when the tab goes hidden and clears it on return; pass a formatter that reads a reactive clock (e.g. `useNow`) so the label stays fresh while the tooltip is open.
45
+
46
+ ```vue
47
+ <TelaPresenceAvatars
48
+ :viewers="[
49
+ { name: 'Ada Lovelace', email: 'ada@example.com' },
50
+ { name: 'Grace Hopper', email: 'grace@example.com', active: false },
51
+ ]"
52
+ count-label="2 Viewing"
53
+ viewing-label="Viewing now"
54
+ away-label="Away"
55
+ />
56
+ ```
57
+
58
+ ### Overflow
59
+
60
+ Only the first `max` viewers render as avatars (priority to active viewers, then the earliest joiners — the input order). The count tooltip lists up to `tooltipMax` emails, then a final "x more" line built with `moreLabel`.
61
+
62
+ ```vue
63
+ <TelaPresenceAvatars
64
+ :viewers="manyViewers"
65
+ :max="3"
66
+ :tooltip-max="7"
67
+ count-label="9 Viewing"
68
+ :more-label="count => `${count} more`"
69
+ />
70
+ ```
71
+
72
+ ### With usePresence
73
+
74
+ The composable joins the live-collaboration room for the channel; `others` is already sorted (active first, then join time) and feeds straight into the component.
75
+
76
+ ```ts
77
+ const { others } = usePresence({
78
+ channel: computed(() => `prompt:${route.params.promptId}`),
79
+ url: 'wss://collab.example.com',
80
+ self: computed(() => ({ id: user.id, email: user.email, name: user.name, image: user.image })),
81
+ getToken: async () => await auth.getToken(),
82
+ })
83
+ ```
84
+
85
+ ```vue
86
+ <TelaPresenceAvatars
87
+ :viewers="others"
88
+ :count-label="`${others.length} Viewing`"
89
+ label="Viewing this canvas"
90
+ />
91
+ ```
92
+
93
+ ## Props
94
+
95
+ | Prop | Type | Default | Description |
96
+ | -------------- | ------------------------------------------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------ |
97
+ | `viewers` | `{ name?: string, email?: string, image?: string, active?: boolean, joinedAt?: number \| null }[]` | required | The other people in the room, pre-sorted (`usePresence` output) |
98
+ | `max` | `number` | `3` | Visible avatar slots |
99
+ | `tooltipMax` | `number` | `7` | Names listed in the count tooltip before the "x more" line |
100
+ | `variant` | `'pill' \| 'avatars'` | `'pill'` | `pill` shows the count on a muted surface; `avatars` is just the stack |
101
+ | `size` | `'2xs' \| 'xs' \| 'sm' \| 'md'` | `'2xs'` pill / `'xs'` avatars | Avatar size |
102
+ | `label` | `string` | `'Viewing this page'` | aria-label suffix; pass a translated string |
103
+ | `countLabel` | `string` | viewer count | Pre-translated count text, e.g. "3 Viewing" (pill variant only) |
104
+ | `viewingLabel` | `string` | `'Viewing now'` | Status word in the avatar tooltip for active viewers |
105
+ | `awayLabel` | `string` | `'Away'` | Status word in the avatar tooltip for hidden-tab viewers |
106
+ | `awaySinceLabel` | `(awaySince: number) => string` | — | Formats the away timestamp (e.g. "3 min ago"); falls back to `awayLabel` |
107
+ | `moreLabel` | `(count: number) => string` | `` count => `${count} more` `` | Builds the overflow line in the count tooltip |
108
+
109
+ ## Features
110
+
111
+ - Pill variant surface: `bg-muted`, 8px radius, 4px vertical / 10px left / 8px right padding, `body-12-medium` count text
112
+ - Overlapping avatars separated by a mask-carved 2px transparent crescent (no hardcoded ring color), overlap proportional to avatar size
113
+ - Falls back to initials when a viewer has no image (via `TelaAvatar`)
114
+ - Count tooltip lists viewer names (capped at `tooltipMax` + "x more"); avatar tooltip stacks the name and Viewing/Away status on separate lines
115
+ - Shared `TelaTooltipGroup`: hovering across triggers skips the tooltip delay/animation
116
+ - Away viewers (`active: false`) dim the avatar to 50% with a 160ms transition and reorder to the back of the stack instead of leaving
117
+ - Subtle 160ms enter/leave/move transitions when viewers join, drop off, or reorder
118
+ - Empty viewers list renders nothing
119
+
120
+ ## Accessibility
121
+
122
+ - Container exposes `role="group"` with an `aria-label` of `"{count} {label}"`
123
+ - Each avatar's accessible name comes from its `alt` (viewer name or email)
@@ -0,0 +1,192 @@
1
+ <script setup lang="ts">
2
+ export type PresenceAvatarSize = '2xs' | 'xs' | 'sm' | 'md'
3
+ export type PresenceAvatarsVariant = 'pill' | 'avatars'
4
+
5
+ export interface PresenceAvatarViewer {
6
+ name?: string
7
+ email?: string
8
+ image?: string
9
+ active?: boolean
10
+ joinedAt?: number | null
11
+ awaySince?: number | null
12
+ }
13
+
14
+ const props = withDefaults(defineProps<{
15
+ viewers: PresenceAvatarViewer[]
16
+ max?: number
17
+ tooltipMax?: number
18
+ size?: PresenceAvatarSize
19
+ variant?: PresenceAvatarsVariant
20
+ label?: string
21
+ countLabel?: string
22
+ viewingLabel?: string
23
+ awayLabel?: string
24
+ awaySinceLabel?: (awaySince: number) => string
25
+ moreLabel?: (count: number) => string
26
+ }>(), {
27
+ max: 3,
28
+ tooltipMax: 7,
29
+ variant: 'pill',
30
+ label: 'Viewing this page',
31
+ viewingLabel: 'Viewing now',
32
+ awayLabel: 'Away',
33
+ moreLabel: (count: number) => `${count} more`,
34
+ })
35
+
36
+ const STACK_METRICS: Record<PresenceAvatarSize, { size: number, overlap: number, fontSize: number }> = {
37
+ '2xs': { size: 16, overlap: 4, fontSize: 8 },
38
+ 'xs': { size: 24, overlap: 6, fontSize: 10 },
39
+ 'sm': { size: 32, overlap: 8, fontSize: 12 },
40
+ 'md': { size: 40, overlap: 10, fontSize: 14 },
41
+ }
42
+
43
+ const resolvedSize = computed<PresenceAvatarSize>(() => props.size ?? (props.variant === 'avatars' ? 'xs' : '2xs'))
44
+
45
+ // Overlapped avatars get a crescent carved out with a mask (same technique as
46
+ // tags-select dots): the mask circle is centered on the next avatar's center,
47
+ // with a 2px ring of transparency around it.
48
+ const stackStyle = computed(() => {
49
+ const { size, overlap } = STACK_METRICS[resolvedSize.value]
50
+ return {
51
+ '--presence-overlap': `${overlap}px`,
52
+ '--presence-mask-radius': `${size / 2 + 2}px`,
53
+ '--presence-mask-x': `calc(100% + ${size / 2 - overlap}px)`,
54
+ }
55
+ })
56
+
57
+ const visibleViewers = computed(() => props.viewers.slice(0, props.max))
58
+ const tooltipViewers = computed(() => props.viewers.slice(0, props.tooltipMax))
59
+ const hiddenTooltipCount = computed(() => Math.max(props.viewers.length - props.tooltipMax, 0))
60
+ const resolvedCountLabel = computed(() => props.countLabel ?? `${props.viewers.length}`)
61
+
62
+ const overflowCount = computed(() => props.variant === 'avatars' ? Math.max(props.viewers.length - props.max, 0) : 0)
63
+
64
+ const overflowTextStyle = computed(() => ({ fontSize: `${STACK_METRICS[resolvedSize.value].fontSize}px` }))
65
+
66
+ function isOverflowSlot(index: number) {
67
+ return overflowCount.value > 0 && index === visibleViewers.value.length - 1
68
+ }
69
+
70
+ function isActive(viewer: PresenceAvatarViewer) {
71
+ return viewer.active !== false
72
+ }
73
+
74
+ function displayName(viewer: PresenceAvatarViewer) {
75
+ return viewer.name || viewer.email || 'Unknown'
76
+ }
77
+
78
+ function viewerKey(viewer: PresenceAvatarViewer, index: number) {
79
+ return viewer.email || viewer.name || `viewer-${index}`
80
+ }
81
+
82
+ function statusLabel(viewer: PresenceAvatarViewer) {
83
+ if (isActive(viewer))
84
+ return props.viewingLabel
85
+ if (viewer.awaySince != null && props.awaySinceLabel)
86
+ return props.awaySinceLabel(viewer.awaySince)
87
+ return props.awayLabel
88
+ }
89
+ </script>
90
+
91
+ <template>
92
+ <TelaTooltipGroup v-if="viewers.length > 0">
93
+ <div
94
+ role="group"
95
+ :aria-label="`${viewers.length} ${label}`"
96
+ flex items-center
97
+ :class="variant === 'pill' ? 'gap-6px rounded-8px bg-muted py-4px pl-10px pr-8px' : ''"
98
+ >
99
+ <TelaTooltipGroupTrigger v-if="variant === 'pill'" side="bottom" trigger-class="flex">
100
+ <span body-12-medium text-primary leading-16px>{{ resolvedCountLabel }}</span>
101
+ <template #content>
102
+ <div flex="~ col" gap-4px text-left>
103
+ <p
104
+ v-for="(viewer, index) in tooltipViewers"
105
+ :key="viewerKey(viewer, index)"
106
+ body-12-regular text-white leading-16px
107
+ >
108
+ {{ displayName(viewer) }}
109
+ </p>
110
+ <p v-if="hiddenTooltipCount > 0" body-12-regular text-white leading-16px>
111
+ {{ moreLabel(hiddenTooltipCount) }}
112
+ </p>
113
+ </div>
114
+ </template>
115
+ </TelaTooltipGroupTrigger>
116
+
117
+ <TransitionGroup name="tela-presence" tag="div" flex items-center :style="stackStyle">
118
+ <div
119
+ v-for="(viewer, index) in visibleViewers"
120
+ :key="viewerKey(viewer, index)"
121
+ class="tela-presence-avatar"
122
+ flex
123
+ >
124
+ <TelaTooltipGroupTrigger side="bottom" trigger-class="flex">
125
+ <div relative flex>
126
+ <TelaAvatar
127
+ :image="viewer.image"
128
+ :alt="displayName(viewer)"
129
+ :size="resolvedSize"
130
+ rounded-full transition-opacity duration-160 ease-out
131
+ :class="isActive(viewer) || isOverflowSlot(index) ? '' : 'op-50'"
132
+ />
133
+ <div
134
+ v-if="isOverflowSlot(index)"
135
+ absolute inset-0 rounded-full
136
+ class="bg-neutral-950/80"
137
+ flex items-center justify-center
138
+ text-white font-500 leading-none select-none
139
+ :style="overflowTextStyle"
140
+ >
141
+ +{{ overflowCount }}
142
+ </div>
143
+ </div>
144
+ <template #content>
145
+ <div v-if="isOverflowSlot(index)" flex="~ col" gap-4px text-left>
146
+ <p
147
+ v-for="(tooltipViewer, tooltipIndex) in tooltipViewers"
148
+ :key="viewerKey(tooltipViewer, tooltipIndex)"
149
+ body-12-regular text-white leading-16px
150
+ >
151
+ {{ displayName(tooltipViewer) }}
152
+ </p>
153
+ <p v-if="hiddenTooltipCount > 0" body-12-regular text-white leading-16px>
154
+ {{ moreLabel(hiddenTooltipCount) }}
155
+ </p>
156
+ </div>
157
+ <div v-else flex="~ col" gap-2px text-left>
158
+ <span body-12-medium text-white>{{ displayName(viewer) }}</span>
159
+ <span body-12-regular text-neutral-200>{{ statusLabel(viewer) }}</span>
160
+ </div>
161
+ </template>
162
+ </TelaTooltipGroupTrigger>
163
+ </div>
164
+ </TransitionGroup>
165
+ </div>
166
+ </TelaTooltipGroup>
167
+ </template>
168
+
169
+ <style scoped>
170
+ .tela-presence-avatar:not(:first-child) {
171
+ margin-left: calc(var(--presence-overlap) * -1);
172
+ }
173
+
174
+ .tela-presence-avatar:not(:last-child) {
175
+ mask-image: radial-gradient(circle var(--presence-mask-radius) at var(--presence-mask-x) center, transparent var(--presence-mask-radius), #fff var(--presence-mask-radius));
176
+ }
177
+
178
+ .tela-presence-enter-active,
179
+ .tela-presence-leave-active {
180
+ transition: opacity 160ms cubic-bezier(0.215, 0.61, 0.355, 1), transform 160ms cubic-bezier(0.215, 0.61, 0.355, 1);
181
+ }
182
+
183
+ .tela-presence-enter-from,
184
+ .tela-presence-leave-to {
185
+ opacity: 0;
186
+ transform: scale(0.8);
187
+ }
188
+
189
+ .tela-presence-move {
190
+ transition: transform 160ms cubic-bezier(0.215, 0.61, 0.355, 1);
191
+ }
192
+ </style>
@@ -0,0 +1,66 @@
1
+ <script setup lang="ts">
2
+ import { TooltipArrow } from 'reka-ui'
3
+ import TelaTooltipContent from './tooltip-content.vue'
4
+
5
+ export type TooltipBodyProps = {
6
+ content?: string
7
+ align?: 'start' | 'center' | 'end'
8
+ side?: 'top' | 'right' | 'bottom' | 'left'
9
+ contentClass?: string
10
+ alignOffset?: number
11
+ arrowOffset?: number
12
+ variant?: 'single' | 'multiline'
13
+ title?: string
14
+ description?: string
15
+ }
16
+
17
+ const props = withDefaults(defineProps<TooltipBodyProps>(), {
18
+ variant: 'single',
19
+ })
20
+
21
+ defineEmits<{
22
+ (e: 'clickContent', event: Event): void
23
+ }>()
24
+
25
+ const variantClasses = computed(() => {
26
+ if (props.variant === 'multiline') {
27
+ return 'w-full min-w-255px max-w-320px py-12px pl-16px pr-24px text-left'
28
+ }
29
+
30
+ return ''
31
+ })
32
+ </script>
33
+
34
+ <template>
35
+ <TelaTooltipContent
36
+ :align="align"
37
+ :side="side"
38
+ :align-offset="props.alignOffset"
39
+ :class="cn(contentClass, variantClasses)"
40
+ @click="$emit('clickContent', $event)"
41
+ >
42
+ <slot>
43
+ <div v-if="variant === 'multiline' && (title || description)" class="max-h-96 overflow-y-auto">
44
+ <h3
45
+ v-if="title"
46
+ body-14-semibold text-white
47
+ >
48
+ {{ title }}
49
+ </h3>
50
+ <p
51
+ v-if="description"
52
+ body-12-regular mt-2px class="text-white/85"
53
+ >
54
+ {{ description }}
55
+ </p>
56
+ </div>
57
+ <template v-else>
58
+ {{ content }}
59
+ </template>
60
+ </slot>
61
+ <TooltipArrow
62
+ class="fill-neutral-800"
63
+ :style="props.arrowOffset ? { transform: `translateX(${props.arrowOffset}px)` } : {}"
64
+ />
65
+ </TelaTooltipContent>
66
+ </template>
@@ -1,16 +1,15 @@
1
1
  <script setup lang="ts">
2
- import { TooltipContent, TooltipPortal, useForwardPropsEmits } from 'reka-ui'
2
+ import { TooltipContent, TooltipPortal, injectTooltipRootContext, useForwardPropsEmits } from 'reka-ui'
3
3
  import type { TooltipContentEmits, TooltipContentProps } from 'reka-ui'
4
4
  import { reactiveOmit } from '@vueuse/core'
5
- import { inject } from 'vue'
6
- import type { HTMLAttributes } from 'vue'
5
+ import type { HTMLAttributes, ShallowRef } from 'vue'
7
6
 
8
7
  defineOptions({
9
8
  inheritAttrs: false,
10
9
  })
11
10
 
12
11
  const props = withDefaults(defineProps<TooltipContentProps & { class?: HTMLAttributes['class'] }>(), {
13
- sideOffset: 4,
12
+ sideOffset: 2,
14
13
  })
15
14
 
16
15
  const emits = defineEmits<TooltipContentEmits & {
@@ -22,22 +21,73 @@ const delegatedProps = reactiveOmit(props, 'class')
22
21
  const forwarded = useForwardPropsEmits(delegatedProps, emits)
23
22
 
24
23
  const disablePortal = inject<boolean>('tela-disable-tooltip-portal', false)
24
+
25
+ const id = Symbol('tela-tooltip')
26
+ const activeId = inject<ShallowRef<symbol | null> | null>('tela-tooltip-active-id', null)
27
+ const { open } = injectTooltipRootContext()
28
+
29
+ watch(open, (isOpen) => {
30
+ if (!activeId) {
31
+ return
32
+ }
33
+
34
+ if (isOpen) {
35
+ activeId.value = id
36
+ }
37
+ else if (activeId.value === id) {
38
+ activeId.value = null
39
+ }
40
+ })
41
+
42
+ const isGrouped = computed(() => activeId !== null && activeId.value !== null && activeId.value !== id)
25
43
  </script>
26
44
 
27
45
  <template>
28
46
  <TooltipPortal :disabled="disablePortal">
29
47
  <TooltipContent
30
48
  v-bind="{ ...forwarded, ...$attrs }"
31
- :style="{ pointerEvents: 'auto' }"
32
- z-1000 overflow-hidden rounded-12px
33
- py-7px px-12px
49
+ z-1000 relative rounded-8px
50
+ py-5px px-10px pointer-events-auto
34
51
  body-12-semibold text-white
35
- shadow-flying
36
- class="bg-[#030C16F0]"
37
- :class="props.class"
52
+ bg-neutral-800
53
+ :class="cn('tooltip-content', props.class)"
54
+ :data-grouped="isGrouped || undefined"
38
55
  @click="$emit('click', $event)"
39
56
  >
40
57
  <slot />
41
58
  </TooltipContent>
42
59
  </TooltipPortal>
43
60
  </template>
61
+
62
+ <style>
63
+ .tooltip-content {
64
+ transform-origin: var(--reka-tooltip-content-transform-origin);
65
+ animation: tooltip-in 0.125s ease-out;
66
+
67
+ &[data-state='closed'] {
68
+ animation: tooltip-out 0.125s ease-out forwards;
69
+ }
70
+
71
+ &[data-state='instant-open'] {
72
+ animation: none;
73
+ }
74
+
75
+ &[data-state='closed'][data-grouped] {
76
+ animation-duration: 0ms;
77
+ }
78
+ }
79
+
80
+ @keyframes tooltip-in {
81
+ from {
82
+ opacity: 0;
83
+ transform: scale(0.97);
84
+ }
85
+ }
86
+
87
+ @keyframes tooltip-out {
88
+ to {
89
+ opacity: 0;
90
+ transform: scale(0.97);
91
+ }
92
+ }
93
+ </style>
@@ -3,6 +3,10 @@ import { TooltipProvider } from 'reka-ui'
3
3
  import type { TooltipProviderProps } from 'reka-ui'
4
4
 
5
5
  const props = defineProps<TooltipProviderProps>()
6
+
7
+ // Tracks which tooltip in this provider is currently open, so a closing tooltip
8
+ // can tell whether a sibling took over (instant swap) or it is just leaving (exit animation).
9
+ provide('tela-tooltip-active-id', shallowRef<symbol | null>(null))
6
10
  </script>
7
11
 
8
12
  <template>
@@ -1,24 +1,14 @@
1
1
  <script setup lang="ts">
2
- import { TooltipArrow } from 'reka-ui'
3
2
  import TelaTooltipProvider from './tooltip-provider.vue'
4
3
  import TelaTooltipRoot from './tooltip-root.vue'
5
4
  import TelaTooltipTrigger from './tooltip-trigger.vue'
6
- import TelaTooltipContent from './tooltip-content.vue'
7
- import { computed } from 'vue'
5
+ import TelaTooltipBody from './tooltip-body.vue'
6
+ import type { TooltipBodyProps } from './tooltip-body.vue'
8
7
 
9
- export type TooltipProps = {
10
- content?: string
11
- align?: 'start' | 'center' | 'end'
12
- side?: 'top' | 'right' | 'bottom' | 'left'
13
- contentClass?: string
8
+ export type TooltipProps = TooltipBodyProps & {
14
9
  disabled?: boolean
15
10
  delayDuration?: number
16
11
  open?: boolean
17
- alignOffset?: number
18
- arrowOffset?: number
19
- variant?: 'single' | 'multiline'
20
- title?: string
21
- description?: string
22
12
  triggerClass?: string
23
13
  }
24
14
 
@@ -40,13 +30,6 @@ const tooltipRootProps = computed(() => {
40
30
 
41
31
  return rootProps
42
32
  })
43
-
44
- const variantClasses = computed(() => {
45
- if (props.variant === 'multiline') {
46
- return 'w-full min-w-255px max-w-320px pt-12px pb-16px px-16px gap-12px flex flex-col text-left max-h-96 overflow-y-auto'
47
- }
48
- return ''
49
- })
50
33
  </script>
51
34
 
52
35
  <template>
@@ -55,36 +38,22 @@ const variantClasses = computed(() => {
55
38
  <TelaTooltipTrigger :class="triggerClass" :tabindex="-1">
56
39
  <slot />
57
40
  </TelaTooltipTrigger>
58
- <TelaTooltipContent
41
+ <TelaTooltipBody
42
+ :content="content"
59
43
  :align="align"
60
44
  :side="side"
61
- :align-offset="props.alignOffset"
62
- :class="cn(contentClass, variantClasses)"
63
- @click="$emit('clickContent', $event)"
45
+ :content-class="contentClass"
46
+ :align-offset="alignOffset"
47
+ :arrow-offset="arrowOffset"
48
+ :variant="variant"
49
+ :title="title"
50
+ :description="description"
51
+ @click-content="$emit('clickContent', $event)"
64
52
  >
65
- <slot name="content">
66
- <div v-if="variant === 'multiline' && (title || description)">
67
- <div
68
- v-if="title"
69
- class="text-white font-inter text-14px leading-16px not-italic font-580 tracking--0.14px"
70
- >
71
- {{ title }}
72
- </div>
73
- <div
74
- v-if="description"
75
- class="body-12-regular text-gray-200 mt-[4px] break-words whitespace-pre-line w-full"
76
- >
77
- {{ description }}
78
- </div>
79
- </div>
80
- <template v-else>
81
- {{ content }}
82
- </template>
83
- </slot>
84
- <TooltipArrow
85
- :style="props.arrowOffset ? { transform: `translateX(${props.arrowOffset}px)` } : {}"
86
- />
87
- </TelaTooltipContent>
53
+ <template v-if="$slots.content" #default>
54
+ <slot name="content" />
55
+ </template>
56
+ </TelaTooltipBody>
88
57
  </TelaTooltipRoot>
89
58
  </TelaTooltipProvider>
90
59
  </template>