@meistrari/tela-build 1.72.0 → 1.73.0
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.
- package/CHANGELOG.md +4 -0
- package/components/tela/avatar/label/avatar-label.mdx +2 -0
- package/components/tela/avatar/label/avatar-label.vue +25 -3
- package/components/tela/copy-button.vue +4 -0
- package/components/tela/dropdown-menu/DropdownMenuItem.vue +2 -2
- package/components/tela/filter/filter-content.test.ts +60 -0
- package/components/tela/filter/filter-content.vue +3 -0
- package/components/tela/filter/filter-trigger.vue +2 -1
- package/components/tela/filter/filter.mdx +13 -1
- package/components/tela/filter/filter.vue +2 -1
- package/components/tela/filter/types.ts +1 -0
- package/components/tela/live-label.vue +36 -20
- package/components/tela/pane.vue +11 -3
- package/components/tela/timeline-waterfall/timeline-waterfall.mdx +85 -8
- package/components/tela/timeline-waterfall/timeline-waterfall.test.ts +917 -0
- package/components/tela/timeline-waterfall/timeline-waterfall.vue +905 -199
- package/components/tela/tooltip/tooltip-body.vue +19 -7
- package/components/tela/tooltip/tooltip.mdx +30 -31
- package/components/tela/tooltip/tooltip.test.ts +46 -0
- package/components/tela/tooltip/tooltip.vue +2 -0
- package/lib/extractors/volar-extract.ts +6 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Latest updates and announcements.
|
|
4
4
|
|
|
5
|
+
## September 17, 2026
|
|
6
|
+
|
|
7
|
+
- **Dropdown menu spacing.** Items with tooltips now use the same icon-to-label gap as other items. See [Dropdown menu](/components/dropdown-menu).
|
|
8
|
+
|
|
5
9
|
## September 16, 2026
|
|
6
10
|
|
|
7
11
|
### Square Loader
|
|
@@ -7,6 +7,8 @@ import * as AvatarLabelStories from './avatar-label.stories.ts';
|
|
|
7
7
|
|
|
8
8
|
An inline avatar + text chip. Pairs a `TelaAvatar` with a text label so any "entity reference" (created-by, assigned-to, owner, reviewer, member, etc.) renders consistently across summary rows, list cells, and metadata strips. The component is named after its **shape**, not a role — pick the role at the call site via the `label`.
|
|
9
9
|
|
|
10
|
+
The avatar retains its configured size in narrow containers. The label stays on one line and truncates with an ellipsis when space is limited; hover the component to read the full label.
|
|
11
|
+
|
|
10
12
|
## Examples
|
|
11
13
|
|
|
12
14
|
### Basic Usage
|
|
@@ -14,15 +14,37 @@ withDefaults(defineProps<Props>(), {
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
|
-
<div
|
|
17
|
+
<div class="avatar-label" :title="label">
|
|
18
18
|
<TelaAvatar
|
|
19
|
+
class="avatar-label-image"
|
|
19
20
|
:size="avatarSize"
|
|
20
|
-
:
|
|
21
|
+
:image="avatarSrc"
|
|
21
22
|
:alt="avatarAlt ?? label"
|
|
22
23
|
rounded-full
|
|
23
24
|
/>
|
|
24
|
-
<p body-12-regular text-secondary>
|
|
25
|
+
<p class="avatar-label-text" body-12-regular text-secondary>
|
|
25
26
|
{{ label }}
|
|
26
27
|
</p>
|
|
27
28
|
</div>
|
|
28
29
|
</template>
|
|
30
|
+
|
|
31
|
+
<style scoped>
|
|
32
|
+
.avatar-label {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: 4px;
|
|
36
|
+
min-width: 0;
|
|
37
|
+
max-width: 100%;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.avatar-label-image {
|
|
41
|
+
flex: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.avatar-label-text {
|
|
45
|
+
min-width: 0;
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
text-overflow: ellipsis;
|
|
48
|
+
white-space: nowrap;
|
|
49
|
+
}
|
|
50
|
+
</style>
|
|
@@ -3,6 +3,8 @@ import type { HTMLAttributes } from 'vue'
|
|
|
3
3
|
|
|
4
4
|
import { UseClipboard } from '@vueuse/components'
|
|
5
5
|
|
|
6
|
+
defineOptions({ inheritAttrs: false })
|
|
7
|
+
|
|
6
8
|
const props = withDefaults(defineProps<{
|
|
7
9
|
content: string
|
|
8
10
|
variant?: 'default' | 'ghost'
|
|
@@ -73,6 +75,8 @@ const transition = {
|
|
|
73
75
|
<slot :copy="() => onClick(copy)">
|
|
74
76
|
<MotionConfig :transition="transition">
|
|
75
77
|
<button
|
|
78
|
+
v-bind="$attrs"
|
|
79
|
+
type="button"
|
|
76
80
|
flex="~ " items-center justify-center
|
|
77
81
|
ease will-change-transform active:scale-94
|
|
78
82
|
class="group duration-80 transition-[background-color,border-color,color,transform]"
|
|
@@ -82,7 +82,7 @@ const shouldShowTooltip = computed(() => !!tooltipConfig.value)
|
|
|
82
82
|
const isIconImage = computed(() => {
|
|
83
83
|
if (!props.icon)
|
|
84
84
|
return false
|
|
85
|
-
return /\.(jpg|jpeg|png|gif|svg|webp)$/i.test(props.icon) || props.icon.startsWith('data:image/')
|
|
85
|
+
return /\.(?:jpg|jpeg|png|gif|svg|webp)$/i.test(props.icon) || props.icon.startsWith('data:image/')
|
|
86
86
|
})
|
|
87
87
|
</script>
|
|
88
88
|
|
|
@@ -103,7 +103,7 @@ const isIconImage = computed(() => {
|
|
|
103
103
|
v-bind="props"
|
|
104
104
|
:class="cn(
|
|
105
105
|
'group outline-none',
|
|
106
|
-
'relative flex cursor-pointer select-none items-center rounded-xl px-3 py-1.5',
|
|
106
|
+
'relative flex gap-10px cursor-pointer select-none items-center rounded-xl px-3 py-1.5',
|
|
107
107
|
'text-body-14-medium font-460 outline-none focus:bg-gray-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-40',
|
|
108
108
|
inset && 'pl-8',
|
|
109
109
|
props.class,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import { computed, defineComponent, nextTick, ref, watch } from 'vue'
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import FilterContent from './filter-content.vue'
|
|
6
|
+
|
|
7
|
+
vi.mock('reka-ui', () => ({
|
|
8
|
+
PopoverContent: defineComponent({
|
|
9
|
+
name: 'PopoverContent',
|
|
10
|
+
props: {
|
|
11
|
+
sideOffset: Number,
|
|
12
|
+
collisionPadding: Number,
|
|
13
|
+
avoidCollisions: Boolean,
|
|
14
|
+
sticky: String,
|
|
15
|
+
},
|
|
16
|
+
template: '<div data-testid="popover-content" :data-side-offset="sideOffset" :data-collision-padding="collisionPadding" :data-avoid-collisions="avoidCollisions" :data-sticky="sticky"><slot /></div>',
|
|
17
|
+
}),
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
vi.stubGlobal('useTemplateRef', () => ref())
|
|
21
|
+
vi.stubGlobal('useElementSize', () => ({ height: ref(0) }))
|
|
22
|
+
vi.stubGlobal('cn', (...values: unknown[]) => values.filter(Boolean).join(' '))
|
|
23
|
+
vi.stubGlobal('computed', computed)
|
|
24
|
+
vi.stubGlobal('nextTick', nextTick)
|
|
25
|
+
vi.stubGlobal('watch', watch)
|
|
26
|
+
|
|
27
|
+
describe('filterContent', () => {
|
|
28
|
+
it('keeps popover content within an 8px collision boundary', () => {
|
|
29
|
+
// Arrange
|
|
30
|
+
const wrapper = mount(FilterContent, {
|
|
31
|
+
props: {
|
|
32
|
+
configs: [],
|
|
33
|
+
closing: false,
|
|
34
|
+
settled: true,
|
|
35
|
+
modelValue: {},
|
|
36
|
+
width: '340px',
|
|
37
|
+
},
|
|
38
|
+
global: {
|
|
39
|
+
mocks: { cn: (...values: unknown[]) => values.filter(Boolean).join(' ') },
|
|
40
|
+
stubs: {
|
|
41
|
+
AnimatePresence: true,
|
|
42
|
+
Motion: true,
|
|
43
|
+
TelaCheckbox: true,
|
|
44
|
+
TelaFilterContentItem: true,
|
|
45
|
+
TelaRadioGroupItem: true,
|
|
46
|
+
TelaScrollArea: true,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Act
|
|
52
|
+
const content = wrapper.getComponent({ name: 'PopoverContent' })
|
|
53
|
+
|
|
54
|
+
// Assert
|
|
55
|
+
expect(content.props('sideOffset')).toBe(4)
|
|
56
|
+
expect(content.props('collisionPadding')).toBe(8)
|
|
57
|
+
expect(content.props('avoidCollisions')).toBe(true)
|
|
58
|
+
expect(content.props('sticky')).toBe('always')
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -186,6 +186,9 @@ function getSlideMotionVariants(index: number, closingState: boolean) {
|
|
|
186
186
|
<template>
|
|
187
187
|
<PopoverContent
|
|
188
188
|
:side-offset="4"
|
|
189
|
+
:collision-padding="8"
|
|
190
|
+
avoid-collisions
|
|
191
|
+
sticky="always"
|
|
189
192
|
align="start"
|
|
190
193
|
data-popover-content
|
|
191
194
|
:data-settled="settled || undefined"
|
|
@@ -24,6 +24,7 @@ defineEmits<{
|
|
|
24
24
|
'focus-visible:ring-2 focus-visible:ring-gray-200 [&:not([data-active])]:hover:bg-subtle data-[active]:bg-muted first:rounded-l-[10px] last:rounded-r-[10px] last:border-r-[0.5px]',
|
|
25
25
|
triggerClass,
|
|
26
26
|
)"
|
|
27
|
+
:aria-label="filter.showCount && count ? `${filter.label}, ${count} selected` : undefined"
|
|
27
28
|
@click="$emit('click')"
|
|
28
29
|
>
|
|
29
30
|
<div inline-flex items-center>
|
|
@@ -31,7 +32,7 @@ defineEmits<{
|
|
|
31
32
|
|
|
32
33
|
<AnimatePresence>
|
|
33
34
|
<Motion
|
|
34
|
-
v-if="count && filter.selectionMode === 'multiple'"
|
|
35
|
+
v-if="count && (filter.selectionMode === 'multiple' || filter.showCount)"
|
|
35
36
|
mt--3px ml-5px
|
|
36
37
|
:initial="{ width: 0, x: -20, scale: 0, opacity: 0 }"
|
|
37
38
|
:animate="{ width: 'auto', x: 0, scale: 1, opacity: 1 }"
|
|
@@ -64,7 +64,7 @@ const selections = ref({
|
|
|
64
64
|
|
|
65
65
|
### Single-Select Filter
|
|
66
66
|
|
|
67
|
-
Set `selectionMode: 'single'` on a filter so it behaves like a radio group — picking an option replaces the prior selection. Options render with `TelaRadioGroupItem` instead of `TelaCheckbox
|
|
67
|
+
Set `selectionMode: 'single'` on a filter so it behaves like a radio group — picking an option replaces the prior selection. Options render with `TelaRadioGroupItem` instead of `TelaCheckbox`. Single-select triggers hide their count badge by default; set `showCount: true` to show the selected count and include it in the trigger's accessible label.
|
|
68
68
|
|
|
69
69
|
```vue
|
|
70
70
|
const filters = [
|
|
@@ -112,6 +112,14 @@ const filters = [
|
|
|
112
112
|
]
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
### Non-Wrapping Filter Bars
|
|
116
|
+
|
|
117
|
+
The bar wraps by default. Use the optional `bar-class` prop when a compact rail must keep its filter triggers on one line. This is backward-compatible: omitting it preserves the existing bar styling and wrapping behavior.
|
|
118
|
+
|
|
119
|
+
```vue
|
|
120
|
+
<TelaFilter v-model="selections" :filters="filters" bar-class="!flex-nowrap" />
|
|
121
|
+
```
|
|
122
|
+
|
|
115
123
|
### Custom Option Rendering
|
|
116
124
|
|
|
117
125
|
Use the scoped `option-{label}` slot to override the default option button. The slot receives `{ option, filter, checked, toggle, mode }`. Useful when options need avatars, icons, secondary text, etc.
|
|
@@ -224,6 +232,8 @@ interface FilterConfig {
|
|
|
224
232
|
options: FilterOption[]
|
|
225
233
|
/** Defaults to `'multiple'`. Single-mode renders radio indicators and replaces (not toggles) the selection. */
|
|
226
234
|
selectionMode?: FilterSelectionMode
|
|
235
|
+
/** Show selected count on this trigger. Multi-select shows counts by default; single-select requires this opt-in. */
|
|
236
|
+
showCount?: boolean
|
|
227
237
|
/** When this filter is active, the popover animates to this width. Falls back to `defaultPopoverWidth`. */
|
|
228
238
|
popoverWidth?: string
|
|
229
239
|
/** Class on the options container (forwarded to `TelaScrollArea`). Use `max-h-[Xpx]` for scrollable lists. */
|
|
@@ -235,6 +245,8 @@ type FilterSelections = Record<string, string[]>
|
|
|
235
245
|
type FilterProps = {
|
|
236
246
|
modelValue?: FilterSelections
|
|
237
247
|
filters: FilterConfig[]
|
|
248
|
+
/** Optional class forwarded to the filter bar. Omitting it preserves default wrapping and styling. */
|
|
249
|
+
barClass?: HTMLAttributes['class']
|
|
238
250
|
triggerClass?: string
|
|
239
251
|
contentClass?: string
|
|
240
252
|
/** Fallback width when the active filter has no own `popoverWidth`. Defaults to `170px`. */
|
|
@@ -10,6 +10,7 @@ type Phase = 'idle' | 'opening' | 'settled' | 'closing'
|
|
|
10
10
|
const props = withDefaults(defineProps<{
|
|
11
11
|
modelValue?: FilterSelections
|
|
12
12
|
filters: FilterConfig[]
|
|
13
|
+
barClass?: HTMLAttributes['class']
|
|
13
14
|
triggerClass?: HTMLAttributes['class']
|
|
14
15
|
contentClass?: HTMLAttributes['class']
|
|
15
16
|
defaultPopoverWidth?: string
|
|
@@ -180,7 +181,7 @@ watch(phase, (newPhase) => {
|
|
|
180
181
|
<MotionConfig :transition="SPRING_CONFIG">
|
|
181
182
|
<PopoverRoot :open="isOpen" @update:open="onOpenChange">
|
|
182
183
|
<PopoverAnchor as-child :reference="referenceEl ?? undefined">
|
|
183
|
-
<TelaFilterBar ref="filterBar"
|
|
184
|
+
<TelaFilterBar ref="filterBar" :class="barClass">
|
|
184
185
|
<TelaFilterTrigger
|
|
185
186
|
v-for="filter in filters"
|
|
186
187
|
:key="filter.key"
|
|
@@ -1,32 +1,48 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<span
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<div relative w-12px h-12px>
|
|
7
|
-
<div
|
|
8
|
-
w-12px h-12px absolute rounded-full bg="positive" flex items-center justify-center opacity-20
|
|
9
|
-
class="blink top-1/2 translate-y--1/2 left-1/2 translate-x--1/2"
|
|
10
|
-
/>
|
|
11
|
-
<div
|
|
12
|
-
w-6px absolute h-6px rounded-full bg="positive"
|
|
13
|
-
class="top-1/2 translate-y--1/2 left-1/2 translate-x--1/2"
|
|
14
|
-
/>
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
2
|
+
<span class="live-label" inline-flex items-center gap-6px body-12-medium text="blue-600">
|
|
3
|
+
<span class="live-dot" aria-hidden="true" />
|
|
4
|
+
<span><slot>Live</slot></span>
|
|
5
|
+
</span>
|
|
17
6
|
</template>
|
|
18
7
|
|
|
19
8
|
<style scoped>
|
|
20
|
-
.
|
|
21
|
-
|
|
9
|
+
.live-label {
|
|
10
|
+
white-space: nowrap;
|
|
22
11
|
}
|
|
23
12
|
|
|
24
|
-
|
|
13
|
+
.live-dot {
|
|
14
|
+
position: relative;
|
|
15
|
+
width: 6px;
|
|
16
|
+
height: 6px;
|
|
17
|
+
flex: none;
|
|
18
|
+
border-radius: 50%;
|
|
19
|
+
background: currentColor;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.live-dot::after {
|
|
23
|
+
content: '';
|
|
24
|
+
position: absolute;
|
|
25
|
+
inset: 0;
|
|
26
|
+
border-radius: inherit;
|
|
27
|
+
background: currentColor;
|
|
28
|
+
animation: live-pulse 1.5s ease-out infinite;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@keyframes live-pulse {
|
|
25
32
|
0% {
|
|
26
|
-
|
|
33
|
+
transform: scale(1);
|
|
34
|
+
opacity: 0.6;
|
|
27
35
|
}
|
|
28
36
|
100% {
|
|
29
|
-
|
|
37
|
+
transform: scale(2.5);
|
|
38
|
+
opacity: 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@media (prefers-reduced-motion: reduce) {
|
|
43
|
+
.live-dot::after {
|
|
44
|
+
animation: none;
|
|
45
|
+
opacity: 0;
|
|
30
46
|
}
|
|
31
47
|
}
|
|
32
48
|
</style>
|
package/components/tela/pane.vue
CHANGED
|
@@ -24,6 +24,7 @@ const size = ref(props.size)
|
|
|
24
24
|
const collapsedSize = ref(props.collapsedSize ?? 7)
|
|
25
25
|
const sizeBeforeCollapse = ref(props.size)
|
|
26
26
|
const isCollapsed = ref(Boolean(props.startCollapsed))
|
|
27
|
+
const paneMinSize = computed(() => isCollapsed.value ? collapsedSize.value : props.minSize)
|
|
27
28
|
|
|
28
29
|
watch(() => props.size, (newValue) => {
|
|
29
30
|
size.value = newValue
|
|
@@ -54,9 +55,13 @@ function toggleCollapse() {
|
|
|
54
55
|
<component
|
|
55
56
|
:is="disableSplit ? 'div' : Pane"
|
|
56
57
|
:size="disableSplit ? undefined : size"
|
|
57
|
-
:min-size="
|
|
58
|
+
:min-size="paneMinSize"
|
|
58
59
|
>
|
|
59
|
-
<div
|
|
60
|
+
<div
|
|
61
|
+
v-if="title || $slots.actions || collapsible"
|
|
62
|
+
flex items-center w-full justify-between relative z-1
|
|
63
|
+
:class="title || $slots.actions ? 'h-72px' : 'h-40px'"
|
|
64
|
+
>
|
|
60
65
|
<Transition name="fade">
|
|
61
66
|
<div v-if="title && !isCollapsed" flex flex-col p-24px>
|
|
62
67
|
<div flex>
|
|
@@ -71,7 +76,10 @@ function toggleCollapse() {
|
|
|
71
76
|
</div>
|
|
72
77
|
</div>
|
|
73
78
|
</Transition>
|
|
74
|
-
<div
|
|
79
|
+
<div
|
|
80
|
+
absolute flex items-center gap-8px
|
|
81
|
+
:class="title || $slots.actions ? 'top-16px right-32px' : 'inset-y-0 right-4px'"
|
|
82
|
+
>
|
|
75
83
|
<slot name="actions" />
|
|
76
84
|
|
|
77
85
|
<TelaButton
|
|
@@ -7,6 +7,8 @@ import * as TimelineWaterfallStories from './timeline-waterfall.stories.ts';
|
|
|
7
7
|
|
|
8
8
|
A waterfall timeline that visualizes hierarchical execution steps over time. Use it to render run-traces, agent debug timelines, request waterfalls, or any sequence of nested operations. Parent rows can collapse/expand their children, bars are positioned against a shared time axis with grid lines and sub-ticks, and the time axis + label column stay sticky while the user scrolls.
|
|
9
9
|
|
|
10
|
+
Rows without children reserve the collapse control's width so icons and labels align with collapsible rows at the same depth.
|
|
11
|
+
|
|
10
12
|
## Example
|
|
11
13
|
|
|
12
14
|
<Canvas of={TimelineWaterfallStories.Default} />
|
|
@@ -23,6 +25,7 @@ const rows: TimelineRow[] = [
|
|
|
23
25
|
label: 'Step 01',
|
|
24
26
|
color: 'gray',
|
|
25
27
|
status: 'success',
|
|
28
|
+
scheduledAt: new Date('2026-05-04T11:59:58Z'),
|
|
26
29
|
startedAt: new Date('2026-05-04T12:00:00Z'),
|
|
27
30
|
finishedAt: new Date('2026-05-04T12:00:03.9Z'),
|
|
28
31
|
durationMs: 3900,
|
|
@@ -58,10 +61,12 @@ const { t } = useI18n()
|
|
|
58
61
|
|
|
59
62
|
const labels = computed(() => ({
|
|
60
63
|
executionTimeline: t('timeline.executionTimeline'),
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
enqueued: t('timeline.enqueued'),
|
|
65
|
+
executed: t('timeline.executed'),
|
|
63
66
|
complete: t('timeline.complete'),
|
|
64
67
|
failed: t('timeline.failed'),
|
|
68
|
+
expand: label => t('timeline.expand', { label }),
|
|
69
|
+
collapse: label => t('timeline.collapse', { label }),
|
|
65
70
|
}))
|
|
66
71
|
</script>
|
|
67
72
|
|
|
@@ -76,6 +81,47 @@ const labels = computed(() => ({
|
|
|
76
81
|
<TelaTimelineWaterfall :rows="rows" hide-header />
|
|
77
82
|
```
|
|
78
83
|
|
|
84
|
+
### Selection vs hover
|
|
85
|
+
|
|
86
|
+
`hoveredId` is a transient pointer/scrub highlight (subtle row tint). `selectedId` is a
|
|
87
|
+
persistent selection — the row that a caller has pinned (e.g. the graph node the user is
|
|
88
|
+
inspecting). It renders a stronger treatment: raised surface, a left accent on the sticky
|
|
89
|
+
label, and an outline around the bars. When a queue is visible, its outline covers the top, bottom, and left edges; the execution outline covers the top, bottom, and right edges, plus short left edges above and below the thin queue connection. The shared connection has no dividing border, and both segments use the same outline opacity. Execution-only rows keep a complete outline. Pass them independently.
|
|
90
|
+
|
|
91
|
+
```vue
|
|
92
|
+
<TelaTimelineWaterfall
|
|
93
|
+
:rows="rows"
|
|
94
|
+
:selected-id="selectedNodeId"
|
|
95
|
+
v-model:hovered-id="hoveredId"
|
|
96
|
+
/>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Unselected queue and execution bars have a subtle half-pixel border in their status accent. A phased execution bar has one continuous outer outline in its aggregate status color, spanning both phases and any real timing gap; individual phases have no border. Their distinct fills and running animations remain inside the outline, without filling the timing gap. Selected bars retain their stronger outline.
|
|
100
|
+
|
|
101
|
+
### Status badges and bar tone
|
|
102
|
+
|
|
103
|
+
Every row includes a compact `TelaStatus` badge. Track bars pair colour with opacity:
|
|
104
|
+
completed rows use a muted success tone, failed rows use a full-strength error tone,
|
|
105
|
+
running rows use a blue animated stripe, waiting rows use a muted warning tone, and
|
|
106
|
+
pending or stopped rows stay subdued. The running stripe respects reduced-motion
|
|
107
|
+
preferences.
|
|
108
|
+
|
|
109
|
+
Rows can provide `scheduledAt` to visualize queue wait before execution. The component
|
|
110
|
+
shows a muted queue segment from `scheduledAt` to `startedAt` only when the wait is at
|
|
111
|
+
least one second. Its tooltip groups queue and execution data into Enqueued and Executed
|
|
112
|
+
rows, each with a timestamp and duration. Omit `scheduledAt` (or pass `null`) for legacy
|
|
113
|
+
and execution-only rows.
|
|
114
|
+
|
|
115
|
+
### Row appearance overrides
|
|
116
|
+
|
|
117
|
+
Set `labelColor: 'primary' | 'secondary'` to control a row label independently of its collapse control. Set `dimmed: true` for a subtle 80% row opacity. Both are optional; callers that omit them keep the existing parent/leaf colors and full row opacity. Workflow Debug uses these overrides to mute pending and skipped rows, and dim skipped rows.
|
|
118
|
+
|
|
119
|
+
### Execution phases
|
|
120
|
+
|
|
121
|
+
Optionally provide `executionSegments` on a row to break its execution bar into labeled phases using the same status colors as standard bars. Set each segment’s `shade` to `default` for the darker status accent or `muted` for an opaque light fill in the same hue. These use distinct shades rather than fading the same pale background, so even a short phase remains visible. Each segment includes its own start/end timestamps, duration, and status. Set the row's start/end and duration to the overall elapsed interval. Segment positions retain real gaps; short phases keep a four-pixel visual minimum. The tooltip shows both phases with matching color keys, timestamps, durations and status badges, including from the queue segment. Running phases use the existing stripe animation with reduced-motion support. Clicking either phase selects the same row.
|
|
122
|
+
|
|
123
|
+
Workflow Debug uses the default shade for the Condition action and the muted shade for its child execution span. Both phases take their color from their own status. Rows without segments keep the standard status-colored bar and existing tooltip.
|
|
124
|
+
|
|
79
125
|
## Props
|
|
80
126
|
|
|
81
127
|
<ArgTypes />
|
|
@@ -83,25 +129,43 @@ const labels = computed(() => ({
|
|
|
83
129
|
```typescript
|
|
84
130
|
type TimelineColor = 'blue' | 'purple' | 'cyan' | 'gray'
|
|
85
131
|
|
|
86
|
-
interface
|
|
132
|
+
interface TimelineExecutionSegment {
|
|
133
|
+
id: string
|
|
134
|
+
label: string
|
|
135
|
+
shade: 'default' | 'muted'
|
|
136
|
+
status: TimelineRow['status']
|
|
137
|
+
startedAt: Date
|
|
138
|
+
finishedAt: Date
|
|
139
|
+
durationMs: number
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface TimelineRow {
|
|
143
|
+
executionSegments?: TimelineExecutionSegment[]
|
|
87
144
|
id: string
|
|
88
145
|
label: string
|
|
146
|
+
labelColor?: 'primary' | 'secondary'
|
|
147
|
+
dimmed?: boolean
|
|
89
148
|
details?: string | null
|
|
90
149
|
icon?: string | null
|
|
91
150
|
color: TimelineColor
|
|
92
|
-
status: 'success' | 'failed'
|
|
151
|
+
status: 'success' | 'failed' | { variant: TelaStatusVariant, label: string }
|
|
152
|
+
scheduledAt?: Date | null
|
|
93
153
|
startedAt: Date | null
|
|
94
154
|
finishedAt: Date | null
|
|
95
155
|
durationMs: number
|
|
156
|
+
showBar?: boolean // false keeps the row visible without a timing bar or tooltip
|
|
96
157
|
costLabel?: string | null
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
children: TimelineChildRow[]
|
|
158
|
+
collapsed?: boolean // initial state only; user expansion survives polling updates
|
|
159
|
+
expandable?: boolean // keep a collapse control while lazy children are loading
|
|
160
|
+
children: TimelineRow[]
|
|
101
161
|
}
|
|
102
162
|
|
|
103
163
|
interface TimelineLabels {
|
|
104
164
|
executionTimeline?: string
|
|
165
|
+
enqueued?: string
|
|
166
|
+
executed?: string
|
|
167
|
+
queuedFor?: string
|
|
168
|
+
scheduledAt?: string
|
|
105
169
|
startedAt?: string
|
|
106
170
|
endedAt?: string
|
|
107
171
|
duration?: string
|
|
@@ -109,6 +173,8 @@ interface TimelineLabels {
|
|
|
109
173
|
stepId?: string
|
|
110
174
|
complete?: string
|
|
111
175
|
failed?: string
|
|
176
|
+
expand?: (label: string) => string
|
|
177
|
+
collapse?: (label: string) => string
|
|
112
178
|
}
|
|
113
179
|
|
|
114
180
|
interface TimelineWaterfallProps {
|
|
@@ -116,5 +182,16 @@ interface TimelineWaterfallProps {
|
|
|
116
182
|
hideHeader?: boolean
|
|
117
183
|
headerLabel?: string
|
|
118
184
|
labels?: TimelineLabels
|
|
185
|
+
hoveredId?: string | null
|
|
186
|
+
selectedId?: string | null
|
|
187
|
+
getRowTestId?: (id: string) => string | undefined
|
|
188
|
+
getExpandTestId?: (id: string) => string | undefined
|
|
189
|
+
formatTimestamp?: (timestamp: Date) => string
|
|
119
190
|
}
|
|
120
191
|
```
|
|
192
|
+
|
|
193
|
+
### Row controls
|
|
194
|
+
|
|
195
|
+
Set `labelWidth` (pixels, default `220`) when the label column needs more room for controls. Debug uses `320` to retain status beside its Map counter.
|
|
196
|
+
|
|
197
|
+
Use the `row-trailing` scoped slot (`{ row }`) to replace the default status beside the row label with compact controls, such as an iteration counter. Controls sit outside the label button; stop click propagation on interactive content to keep it independent of row selection. Set `expandable: true` on a row with lazy children so its collapse control remains available before those children arrive.
|