@vllnt/ui 0.2.1-canary.ab2c69a → 0.2.1-canary.b3f1dff

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,83 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ forwardRef
5
+ } from "react";
6
+ import { cn } from "../../lib/utils";
7
+ const KIND_GLYPH = {
8
+ agent: "\u25C7",
9
+ artifact: "\u25CC",
10
+ input: "\u2198",
11
+ output: "\u2197",
12
+ run: "\u25B6",
13
+ task: "\u25A2"
14
+ };
15
+ const KIND_LABEL = {
16
+ agent: "Agent",
17
+ artifact: "Artifact",
18
+ input: "Input",
19
+ output: "Output",
20
+ run: "Run",
21
+ task: "Task"
22
+ };
23
+ const DEFAULT_LABELS = {
24
+ region: "Playback ghost"
25
+ };
26
+ const clamp01 = (v) => {
27
+ if (v < 0) {
28
+ return 0;
29
+ }
30
+ if (v > 1) {
31
+ return 1;
32
+ }
33
+ return v;
34
+ };
35
+ const PlaybackGhost = forwardRef(
36
+ (props, ref) => {
37
+ const {
38
+ className,
39
+ kind,
40
+ label,
41
+ labels,
42
+ opacity = 0.4,
43
+ size = 40,
44
+ x,
45
+ y,
46
+ ...rest
47
+ } = props;
48
+ const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
49
+ const safeOpacity = clamp01(opacity);
50
+ const safeSize = size < 16 ? 16 : size;
51
+ const ariaLabel = kind ? `${resolvedLabels.region}: ${KIND_LABEL[kind]}` : resolvedLabels.region;
52
+ return /* @__PURE__ */ jsxs(
53
+ "div",
54
+ {
55
+ "aria-label": ariaLabel,
56
+ className: cn(
57
+ "pointer-events-none absolute z-10 inline-flex -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-1.5 rounded-md border border-dashed border-border/70 bg-background/40 px-2 py-1 text-xs text-foreground backdrop-blur-sm",
58
+ className
59
+ ),
60
+ "data-playback-ghost": true,
61
+ "data-playback-kind": kind ?? "unknown",
62
+ ref,
63
+ role: "img",
64
+ style: {
65
+ left: x,
66
+ minHeight: safeSize,
67
+ minWidth: safeSize,
68
+ opacity: safeOpacity,
69
+ top: y
70
+ },
71
+ ...rest,
72
+ children: [
73
+ kind ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-muted-foreground", children: KIND_GLYPH[kind] }) : null,
74
+ label ? /* @__PURE__ */ jsx("span", { className: "truncate", "data-playback-ghost-label": true, children: label }) : null
75
+ ]
76
+ }
77
+ );
78
+ }
79
+ );
80
+ PlaybackGhost.displayName = "PlaybackGhost";
81
+ export {
82
+ PlaybackGhost
83
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ PresenceSyncIndicator
3
+ } from "./presence-sync-indicator";
4
+ export {
5
+ PresenceSyncIndicator
6
+ };
@@ -0,0 +1,73 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ forwardRef
5
+ } from "react";
6
+ import { cn } from "../../lib/utils";
7
+ const STATE_LABEL = {
8
+ error: "Sync error",
9
+ live: "Live",
10
+ offline: "Offline",
11
+ reconnecting: "Reconnecting",
12
+ syncing: "Syncing"
13
+ };
14
+ const STATE_DOT = {
15
+ error: "bg-red-500",
16
+ live: "bg-emerald-500",
17
+ offline: "bg-muted-foreground",
18
+ reconnecting: "bg-amber-500 animate-pulse",
19
+ syncing: "bg-blue-500 animate-pulse"
20
+ };
21
+ const STATE_TEXT = {
22
+ error: "text-red-700 dark:text-red-300",
23
+ live: "text-emerald-700 dark:text-emerald-300",
24
+ offline: "text-muted-foreground",
25
+ reconnecting: "text-amber-700 dark:text-amber-300",
26
+ syncing: "text-blue-700 dark:text-blue-300"
27
+ };
28
+ const DEFAULT_LABELS = {
29
+ region: "Presence sync"
30
+ };
31
+ const PresenceSyncIndicator = forwardRef((props, ref) => {
32
+ const { className, label, labels, state, status, ...rest } = props;
33
+ const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
34
+ const text = label ?? STATE_LABEL[state];
35
+ return /* @__PURE__ */ jsxs(
36
+ "div",
37
+ {
38
+ "aria-label": `${resolvedLabels.region}: ${STATE_LABEL[state]}`,
39
+ className: cn(
40
+ "inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-2 py-1 text-[11px] shadow-sm",
41
+ className
42
+ ),
43
+ "data-presence-state": state,
44
+ "data-presence-sync": true,
45
+ ref,
46
+ role: "status",
47
+ ...rest,
48
+ children: [
49
+ /* @__PURE__ */ jsx(
50
+ "span",
51
+ {
52
+ "aria-hidden": "true",
53
+ className: cn("h-1.5 w-1.5 rounded-full", STATE_DOT[state]),
54
+ "data-presence-sync-dot": true
55
+ }
56
+ ),
57
+ /* @__PURE__ */ jsx("span", { className: cn("font-medium", STATE_TEXT[state]), children: text }),
58
+ status ? /* @__PURE__ */ jsx(
59
+ "span",
60
+ {
61
+ className: "text-[10px] text-muted-foreground",
62
+ "data-presence-sync-status": true,
63
+ children: status
64
+ }
65
+ ) : null
66
+ ]
67
+ }
68
+ );
69
+ });
70
+ PresenceSyncIndicator.displayName = "PresenceSyncIndicator";
71
+ export {
72
+ PresenceSyncIndicator
73
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ RunTimeline
3
+ } from "./run-timeline";
4
+ export {
5
+ RunTimeline
6
+ };
@@ -0,0 +1,221 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ forwardRef
5
+ } from "react";
6
+ import { cn } from "../../lib/utils";
7
+ const STATE_FILL = {
8
+ complete: "bg-emerald-500/70",
9
+ failed: "bg-red-500/70",
10
+ queued: "bg-amber-500/70",
11
+ running: "bg-blue-500/70",
12
+ stopped: "bg-muted-foreground/40"
13
+ };
14
+ const STATE_LABEL = {
15
+ complete: "Complete",
16
+ failed: "Failed",
17
+ queued: "Queued",
18
+ running: "Running",
19
+ stopped: "Stopped"
20
+ };
21
+ const DEFAULT_LABELS = {
22
+ empty: "No phases",
23
+ region: "Run timeline"
24
+ };
25
+ const clamp = (v, min, max) => {
26
+ if (v < min) {
27
+ return min;
28
+ }
29
+ if (v > max) {
30
+ return max;
31
+ }
32
+ return v;
33
+ };
34
+ const Endpoints = (props) => {
35
+ const fmt = props.format;
36
+ const showCursor = typeof props.cursor === "number";
37
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-2 text-[11px] text-muted-foreground", children: [
38
+ /* @__PURE__ */ jsx("span", { "data-run-timeline-start": true, children: fmt ? fmt(props.start) : props.start }),
39
+ showCursor ? /* @__PURE__ */ jsx(
40
+ "span",
41
+ {
42
+ className: "font-semibold text-foreground",
43
+ "data-run-timeline-cursor-label": true,
44
+ children: fmt ? fmt(props.cursor ?? 0) : props.cursor
45
+ }
46
+ ) : null,
47
+ /* @__PURE__ */ jsx("span", { "data-run-timeline-end": true, children: fmt ? fmt(props.end) : props.end })
48
+ ] });
49
+ };
50
+ const PhaseBar = (props) => {
51
+ const { laneIndex, laneTotal, phase, span, start } = props;
52
+ const left = clamp((phase.start - start) / span, 0, 1) * 100;
53
+ const right = clamp((phase.end - start) / span, 0, 1) * 100;
54
+ const width = Math.max(right - left, 0.5);
55
+ const top = laneIndex / laneTotal * 100;
56
+ const height = 100 / laneTotal;
57
+ const state = phase.state ?? "running";
58
+ const sharedStyle = {
59
+ height: `${height}%`,
60
+ left: `${left}%`,
61
+ top: `${top}%`,
62
+ width: `${width}%`
63
+ };
64
+ const ariaLabel = `${STATE_LABEL[state]} ${phase.start} \u2192 ${phase.end}`;
65
+ if (phase.onActivate) {
66
+ const handleClick = () => {
67
+ phase.onActivate?.();
68
+ };
69
+ return /* @__PURE__ */ jsx(
70
+ "button",
71
+ {
72
+ "aria-label": ariaLabel,
73
+ className: cn(
74
+ "absolute flex items-center justify-start overflow-hidden truncate rounded-sm border border-border/50 px-1 text-left text-[10px] text-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
75
+ STATE_FILL[state]
76
+ ),
77
+ "data-run-phase": phase.id,
78
+ "data-run-phase-state": state,
79
+ onClick: handleClick,
80
+ style: sharedStyle,
81
+ type: "button",
82
+ children: phase.label
83
+ }
84
+ );
85
+ }
86
+ return /* @__PURE__ */ jsx(
87
+ "span",
88
+ {
89
+ "aria-label": ariaLabel,
90
+ className: cn(
91
+ "absolute flex items-center justify-start overflow-hidden truncate rounded-sm border border-border/50 px-1 text-[10px] text-foreground",
92
+ STATE_FILL[state]
93
+ ),
94
+ "data-run-phase": phase.id,
95
+ "data-run-phase-state": state,
96
+ role: "img",
97
+ style: sharedStyle,
98
+ children: phase.label
99
+ }
100
+ );
101
+ };
102
+ const TrackBody = (props) => {
103
+ const { cursor, end, lanes, phases, start } = props;
104
+ const span = end - start;
105
+ const cursorRatio = typeof cursor === "number" ? clamp((cursor - start) / span, 0, 1) : null;
106
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-stretch", children: [
107
+ /* @__PURE__ */ jsx("div", { className: "flex w-24 flex-col gap-px text-[10px] uppercase tracking-wide text-muted-foreground", children: lanes.map((lane) => /* @__PURE__ */ jsx(
108
+ "div",
109
+ {
110
+ className: "flex h-7 items-center px-2",
111
+ "data-run-timeline-lane": lane.id,
112
+ children: lane.label
113
+ },
114
+ lane.id
115
+ )) }),
116
+ /* @__PURE__ */ jsxs(
117
+ "div",
118
+ {
119
+ className: "relative flex-1 overflow-hidden rounded-md border border-border bg-muted/20",
120
+ "data-run-timeline-track": true,
121
+ style: { height: `${lanes.length * 28}px` },
122
+ children: [
123
+ phases.map((phase) => {
124
+ const index = lanes.findIndex(
125
+ (lane) => lane.id === (phase.laneId ?? "default")
126
+ );
127
+ if (index === -1) {
128
+ return null;
129
+ }
130
+ return /* @__PURE__ */ jsx(
131
+ PhaseBar,
132
+ {
133
+ laneIndex: index,
134
+ laneTotal: lanes.length,
135
+ phase,
136
+ span,
137
+ start
138
+ },
139
+ phase.id
140
+ );
141
+ }),
142
+ cursorRatio === null ? null : /* @__PURE__ */ jsx(
143
+ "span",
144
+ {
145
+ "aria-hidden": "true",
146
+ className: "absolute top-0 bottom-0 w-px bg-foreground",
147
+ "data-run-timeline-cursor": true,
148
+ style: { left: `${cursorRatio * 100}%` }
149
+ }
150
+ )
151
+ ]
152
+ }
153
+ )
154
+ ] });
155
+ };
156
+ const resolveLanes = (explicit) => {
157
+ if (explicit && explicit.length > 0) {
158
+ return explicit;
159
+ }
160
+ return [{ id: "default", label: "Run" }];
161
+ };
162
+ const RunTimeline = forwardRef(
163
+ (props, ref) => {
164
+ const {
165
+ className,
166
+ cursor,
167
+ end,
168
+ formatValue,
169
+ labels,
170
+ lanes,
171
+ phases,
172
+ start,
173
+ ...rest
174
+ } = props;
175
+ const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
176
+ const safeEnd = end <= start ? start + 1 : end;
177
+ const resolvedLanes = resolveLanes(lanes);
178
+ return /* @__PURE__ */ jsxs(
179
+ "section",
180
+ {
181
+ "aria-label": resolvedLabels.region,
182
+ className: cn("flex w-full flex-col gap-2", className),
183
+ "data-run-timeline": true,
184
+ ref,
185
+ ...rest,
186
+ children: [
187
+ /* @__PURE__ */ jsx(
188
+ Endpoints,
189
+ {
190
+ cursor,
191
+ end: safeEnd,
192
+ format: formatValue,
193
+ start
194
+ }
195
+ ),
196
+ phases.length === 0 ? /* @__PURE__ */ jsx(
197
+ "p",
198
+ {
199
+ className: "rounded-md border border-border bg-muted/20 px-2 py-3 text-center text-[11px] text-muted-foreground",
200
+ "data-run-timeline-state": "empty",
201
+ children: resolvedLabels.empty
202
+ }
203
+ ) : /* @__PURE__ */ jsx(
204
+ TrackBody,
205
+ {
206
+ cursor,
207
+ end: safeEnd,
208
+ lanes: resolvedLanes,
209
+ phases,
210
+ start
211
+ }
212
+ )
213
+ ]
214
+ }
215
+ );
216
+ }
217
+ );
218
+ RunTimeline.displayName = "RunTimeline";
219
+ export {
220
+ RunTimeline
221
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ StateBadgeOverlay
3
+ } from "./state-badge-overlay";
4
+ export {
5
+ StateBadgeOverlay
6
+ };
@@ -0,0 +1,90 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ forwardRef
5
+ } from "react";
6
+ import { cn } from "../../lib/utils";
7
+ const STATE_LABEL = {
8
+ complete: "Complete",
9
+ failed: "Failed",
10
+ idle: "Idle",
11
+ queued: "Queued",
12
+ running: "Running",
13
+ stopped: "Stopped"
14
+ };
15
+ const STATE_TONE = {
16
+ complete: "border-emerald-500/40 bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
17
+ failed: "border-red-500/40 bg-red-500/15 text-red-700 dark:text-red-300",
18
+ idle: "border-border bg-muted/40 text-muted-foreground",
19
+ queued: "border-amber-500/40 bg-amber-500/15 text-amber-700 dark:text-amber-300",
20
+ running: "border-blue-500/40 bg-blue-500/15 text-blue-700 dark:text-blue-300",
21
+ stopped: "border-border bg-background text-foreground"
22
+ };
23
+ const STATE_DOT = {
24
+ complete: "bg-emerald-500",
25
+ failed: "bg-red-500",
26
+ idle: "bg-muted-foreground",
27
+ queued: "bg-amber-500",
28
+ running: "bg-blue-500 animate-pulse",
29
+ stopped: "bg-muted-foreground"
30
+ };
31
+ const ANCHOR_TRANSFORM = {
32
+ "bottom-left": "translate(-100%, 100%)",
33
+ "bottom-right": "translate(0%, 100%)",
34
+ "top-left": "translate(-100%, -100%)",
35
+ "top-right": "translate(0%, -100%)"
36
+ };
37
+ const DEFAULT_LABELS = {
38
+ region: "State"
39
+ };
40
+ const StateBadgeOverlay = forwardRef((props, ref) => {
41
+ const {
42
+ anchor = "top-right",
43
+ className,
44
+ label,
45
+ labels,
46
+ state,
47
+ x,
48
+ y,
49
+ ...rest
50
+ } = props;
51
+ const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
52
+ const text = label ?? STATE_LABEL[state];
53
+ return /* @__PURE__ */ jsxs(
54
+ "div",
55
+ {
56
+ "aria-label": `${resolvedLabels.region}: ${STATE_LABEL[state]}`,
57
+ className: cn(
58
+ "pointer-events-none absolute z-20 inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide shadow-sm backdrop-blur",
59
+ STATE_TONE[state],
60
+ className
61
+ ),
62
+ "data-state-anchor": anchor,
63
+ "data-state-badge-overlay": true,
64
+ "data-state-name": state,
65
+ ref,
66
+ role: "status",
67
+ style: {
68
+ left: x,
69
+ top: y,
70
+ transform: ANCHOR_TRANSFORM[anchor]
71
+ },
72
+ ...rest,
73
+ children: [
74
+ /* @__PURE__ */ jsx(
75
+ "span",
76
+ {
77
+ "aria-hidden": "true",
78
+ className: cn("h-1.5 w-1.5 rounded-full", STATE_DOT[state]),
79
+ "data-state-badge-dot": true
80
+ }
81
+ ),
82
+ text
83
+ ]
84
+ }
85
+ );
86
+ });
87
+ StateBadgeOverlay.displayName = "StateBadgeOverlay";
88
+ export {
89
+ StateBadgeOverlay
90
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ TimelineScrubber
3
+ } from "./timeline-scrubber";
4
+ export {
5
+ TimelineScrubber
6
+ };
@@ -0,0 +1,179 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ forwardRef,
5
+ useId
6
+ } from "react";
7
+ import { cn } from "../../lib/utils";
8
+ const TONE_FILL = {
9
+ danger: "bg-red-500",
10
+ neutral: "bg-foreground",
11
+ primary: "bg-blue-500",
12
+ success: "bg-emerald-500",
13
+ warn: "bg-amber-500"
14
+ };
15
+ const DEFAULT_LABELS = {
16
+ region: "Timeline scrubber"
17
+ };
18
+ const clamp = (v, min, max) => {
19
+ if (v < min) {
20
+ return min;
21
+ }
22
+ if (v > max) {
23
+ return max;
24
+ }
25
+ return v;
26
+ };
27
+ const Labels = (props) => {
28
+ const fmt = props.formatValue;
29
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [
30
+ /* @__PURE__ */ jsx("span", { "data-timeline-scrubber-start": true, children: fmt ? fmt(props.start) : props.start }),
31
+ /* @__PURE__ */ jsx(
32
+ "span",
33
+ {
34
+ className: "font-semibold text-foreground",
35
+ "data-timeline-scrubber-cursor": true,
36
+ children: fmt ? fmt(props.clamped) : props.clamped
37
+ }
38
+ ),
39
+ /* @__PURE__ */ jsx("span", { "data-timeline-scrubber-end": true, children: fmt ? fmt(props.end) : props.end })
40
+ ] });
41
+ };
42
+ const Track = (props) => /* @__PURE__ */ jsxs("div", { className: "relative h-6", children: [
43
+ /* @__PURE__ */ jsx(
44
+ "span",
45
+ {
46
+ "aria-hidden": "true",
47
+ className: "absolute left-0 right-0 top-1/2 h-1 -translate-y-1/2 rounded-full bg-muted"
48
+ }
49
+ ),
50
+ /* @__PURE__ */ jsx(
51
+ "span",
52
+ {
53
+ "aria-hidden": "true",
54
+ className: cn(
55
+ "absolute left-0 top-1/2 h-1 -translate-y-1/2 rounded-full",
56
+ TONE_FILL[props.tone]
57
+ ),
58
+ "data-timeline-scrubber-fill": true,
59
+ style: { width: `${props.ratio * 100}%` }
60
+ }
61
+ ),
62
+ props.ticks?.map((tick) => /* @__PURE__ */ jsx(
63
+ TickMark,
64
+ {
65
+ scrubberId: props.scrubberId,
66
+ span: props.span,
67
+ start: props.start,
68
+ tick
69
+ },
70
+ tick.id
71
+ )),
72
+ /* @__PURE__ */ jsx(
73
+ "input",
74
+ {
75
+ "aria-label": props.ariaLabel,
76
+ className: "absolute inset-0 h-full w-full cursor-pointer appearance-none bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
77
+ "data-timeline-scrubber-input": true,
78
+ id: props.inputId,
79
+ max: props.max,
80
+ min: props.min,
81
+ onChange: props.onChange,
82
+ step: props.step,
83
+ type: "range",
84
+ value: props.value
85
+ }
86
+ )
87
+ ] });
88
+ const TickMark = (props) => {
89
+ const { scrubberId, span, start, tick } = props;
90
+ const ratio = clamp((tick.value - start) / span, 0, 1);
91
+ const tone = tick.tone ?? "neutral";
92
+ return /* @__PURE__ */ jsx(
93
+ "span",
94
+ {
95
+ "aria-hidden": "true",
96
+ className: cn(
97
+ "absolute top-1/2 h-2.5 w-px -translate-y-1/2 rounded-full",
98
+ TONE_FILL[tone]
99
+ ),
100
+ "data-scrubber-tick": tick.id,
101
+ "data-scrubber-tick-of": scrubberId,
102
+ "data-scrubber-tick-tone": tone,
103
+ style: { left: `${ratio * 100}%` },
104
+ title: typeof tick.label === "string" ? tick.label : void 0
105
+ }
106
+ );
107
+ };
108
+ const TimelineScrubber = forwardRef((props, ref) => {
109
+ const {
110
+ className,
111
+ end,
112
+ formatValue,
113
+ labels,
114
+ onValueChange,
115
+ start,
116
+ step = 1,
117
+ ticks,
118
+ tone = "primary",
119
+ value,
120
+ ...rest
121
+ } = props;
122
+ const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
123
+ const inputId = useId();
124
+ const safeEnd = end <= start ? start + 1 : end;
125
+ const span = safeEnd - start;
126
+ const clamped = clamp(value, start, safeEnd);
127
+ const ratio = clamp((clamped - start) / span, 0, 1);
128
+ const handleChange = (event) => {
129
+ onValueChange(clamp(Number(event.target.value), start, safeEnd));
130
+ };
131
+ return /* @__PURE__ */ jsxs(
132
+ "div",
133
+ {
134
+ "aria-label": resolvedLabels.region,
135
+ className: cn(
136
+ "flex w-full flex-col gap-1 text-[11px] text-muted-foreground",
137
+ className
138
+ ),
139
+ "data-timeline-scrubber": true,
140
+ "data-timeline-tone": tone,
141
+ ref,
142
+ role: "group",
143
+ ...rest,
144
+ children: [
145
+ /* @__PURE__ */ jsx(
146
+ Labels,
147
+ {
148
+ clamped,
149
+ end: safeEnd,
150
+ formatValue,
151
+ start
152
+ }
153
+ ),
154
+ /* @__PURE__ */ jsx(
155
+ Track,
156
+ {
157
+ ariaLabel: resolvedLabels.region,
158
+ inputId,
159
+ max: safeEnd,
160
+ min: start,
161
+ onChange: handleChange,
162
+ ratio,
163
+ scrubberId: inputId,
164
+ span,
165
+ start,
166
+ step,
167
+ ticks,
168
+ tone,
169
+ value: clamped
170
+ }
171
+ )
172
+ ]
173
+ }
174
+ );
175
+ });
176
+ TimelineScrubber.displayName = "TimelineScrubber";
177
+ export {
178
+ TimelineScrubber
179
+ };